Functions and imports

fileprefix = "20210717_"

ref_file_mm10 = "~/Documents/reference_data/mouse/ensembl/ensembl_gene_length_mm10.txt.gz"

# This is required to set the fonts of the paper plots to Arial
library(extrafont)
font_import(prompt=FALSE)
loadfonts()

library(RColorBrewer)
library(Seurat)
library(scater)
library(cowplot)
library(dplyr)
library(tidyr)
library(SoupX)

calc_tpm = function(dat, ref_file) {
  mm_annot = read.table(ref_file, header=T, stringsAsFactors=F, sep="\t")
  eff_length = mm_annot$eff_length[match(rownames(dat), mm_annot$mgi_symbol)]
  eff_length[!is.finite(eff_length)] = 1
  
  sce = SingleCellExperiment(assays=list(counts=as.matrix(dat), logcounts=log2(as.matrix(dat)+1)))
  tpm = calculateTPM(sce, eff_length)
  return(tpm)
}

theme_publication_plot = function(p, legend_title, legend_aes=4) {
  p = p +
  theme_cowplot() + 
  theme(axis.text = element_text(family="Arial", colour="black",size=10,face="plain"),
                       axis.title = element_text(family="Arial", colour="black",size=12,face="plain"),
                       strip.text.x = element_text(family="Arial", colour="black",size=12,face="plain", angle=90),
                       strip.text.y = element_text(family="Arial", colour="black",size=12,face="plain", angle=360),
                       strip.background = element_blank(),
                       panel.spacing.x = unit(1, "mm"),
                       panel.spacing.y = unit(3, "mm"),
                       plot.title = element_text(family="Arial", colour="black",size=12,face="plain",hjust = 0.5))
  if (!is.null(legend_title)) {
     p = p + guides(fill = guide_legend(title=legend_title,
                              override.aes = list(size=legend_aes)),
                            colour = guide_legend(title=legend_title,
                              override.aes = list(size=legend_aes)))
  }
  return(p)
}

Load the data

samplenames = c("EA_WT_1", "EA_WT_2", "EA_NOTCH1_HOM_1", "EA_NOTCH1_HOM_2")
library_names = c("WT 1", "WT 2", "HOM KO 1", "HOM KO 2")
prefixes = c("c1_", "c2_", "s1_", "s2_")
expr_matrices = list()
# celltypes_all = list()
for (i in 1:length(samplenames)) {
  prefix = prefixes[i]
  in_path = file.path("../cellranger/", samplenames[i])
  sc = load10X(in_path)
  
  out = sc$toc
  colnames(out) = paste0(prefix, colnames(out))

  expr_matrices[[length(expr_matrices)+1]] = out
  
  # This loads a previous set of cell type assignments that was used to compare the new setup
  # celltypes = read.table(file.path("../identify_celltypes", samplenames[i], "celltypes_per_cell.txt"), header=T, stringsAsFactors=F)
  # celltypes$cell = paste0(prefix, celltypes$cell)
  # celltypes_all[[length(celltypes_all)+1]] = celltypes
}

Read in some CellRanger stats

stats = list()
for (i in 1:length(samplenames)) {
  stats_sample = read.table(file.path("../cellranger/", samplenames[i], "metrics_summary.csv"), sep=",", header=T, stringsAsFactors=F)
  stats_sample$library = samplenames[i]
  stats[[samplenames[i]]] = stats_sample
}
stats = do.call(rbind, stats)
print(stats)
Registered S3 method overwritten by 'cli':
  method     from         
  print.boxx spatstat.geom

Explore to identify filters

Here we plot a summary of general QC metrics to identify unhappy cells to be filtered

nFeature_RNA_min = 2500
nFeature_RNA_max = 6500
nCount_RNA_max = 55000
prop.mt_min = 0.03
prop.mt_max = 0.1

seu = list()
for (i in 1:length(expr_matrices)) {
  seu[[i]] = CreateSeuratObject(counts = expr_matrices[[i]], min.cells = 0, min.features = 0)
  
  mito.genes = rownames(GetAssayData(object=seu[[i]]))[grepl(pattern = "^MT-", x = toupper(rownames(GetAssayData(object=seu[[i]]))))]
  percent.mito = Matrix::colSums(GetAssayData(object=seu[[i]], slot="counts")[mito.genes, ]) / Matrix::colSums(GetAssayData(object=seu[[i]], slot="counts"))
  seu[[i]] = AddMetaData(object = seu[[i]], metadata = percent.mito, col.name = "prop.mt")
  
  # plot prop.mt vs nFeature_RNA and vs nCount_RNA
  p1 = ggplot(seu[[i]]@meta.data) + aes(x=prop.mt, y=nFeature_RNA) + geom_point(size=0.1, colour="red") + scale_y_log10() + theme_cowplot() + geom_vline(xintercept=c(prop.mt_min, prop.mt_max), linetype=2) + geom_hline(yintercept=c(nFeature_RNA_min, nFeature_RNA_max), linetype=2)
  
  p2 = ggplot(seu[[i]]@meta.data) + aes(x=prop.mt, y=nCount_RNA) + geom_point(size=0.1, colour="blue") + scale_y_log10() + theme_cowplot() + geom_vline(xintercept=c(prop.mt_min, prop.mt_max), linetype=2) + geom_hline(yintercept=c(nCount_RNA_max), linetype=2)
  
  print(plot_grid(p1, p2))
  
  p1 = VlnPlot(seu[[i]], group.by="orig.ident", features = c( "nFeature_RNA"), ncol = 1, pt.size=0) +
    geom_hline(yintercept = c(nFeature_RNA_min, nFeature_RNA_max), linetype=2) + theme(legend.position="none")
  p2 = VlnPlot(seu[[i]], group.by="orig.ident", features = c( "nCount_RNA"), ncol = 1, pt.size=0) +
    geom_hline(yintercept = c(nCount_RNA_max), linetype=2) + theme(legend.position="none")
  p3 = VlnPlot(seu[[i]], group.by="orig.ident", features = c( "prop.mt"), ncol = 1, pt.size=0) + theme(legend.position="none") + geom_hline(yintercept = c(prop.mt_max, prop.mt_min), linetype=2)
  
  # if (grepl("KO", samplenames[i], fixed=T)) {
  #   p3 = p3 + geom_hline(yintercept = c(0.1, 0.002), linetype=2)
  # } else {
  #   p3 = p3 + geom_hline(yintercept = c(0.1, 0.01), linetype=2)
  # }
  
  print(plot_grid(p1, p2, p3, ncol=3))
}

All cells

Apply batch correction - MT filters and adjust for MT, nGenes and cell cycle

seu = list()
for (i in 1:length(expr_matrices)) {
  seu[[i]] = CreateSeuratObject(counts = expr_matrices[[i]], min.cells = 30, min.features = 2500)
  
  mito.genes = rownames(GetAssayData(object=seu[[i]]))[grepl(pattern = "^MT-", x = toupper(rownames(GetAssayData(object=seu[[i]]))))]
  percent.mito = Matrix::colSums(GetAssayData(object=seu[[i]], slot="counts")[mito.genes, ]) / Matrix::colSums(GetAssayData(object=seu[[i]], slot="counts"))
  seu[[i]] = AddMetaData(object = seu[[i]], metadata = percent.mito, col.name = "prop.mt")
  
  # Restrict proportion MT expression to remove unhappy cells
  seu[[i]] = subset(x = seu[[i]], subset = prop.mt > 0.03 & prop.mt < 0.1)
  
  # Remove potential doublets
  seu[[i]] = subset(x = seu[[i]], subset = nFeature_RNA < 6500 & nCount_RNA < 55000)
  
  seu[[i]] = SCTransform(seu[[i]], verbose = FALSE, vars.to.regress=c("prop.mt", "nFeature_RNA", "nCount_RNA"))
  seu[[i]] = CellCycleScoring(seu[[i]], s.features = cc.genes$s.genes, g2m.features = cc.genes$g2m.genes, assay = 'SCT', set.ident = TRUE)
  # Enable this when also adjusting for cell cycle
  seu[[i]] = SCTransform(seu[[i]], verbose = FALSE, vars.to.regress=c("prop.mt", "nFeature_RNA", "nCount_RNA", "S.Score", "G2M.Score"))
  
  seu[[i]] = AddMetaData(object=seu[[i]], metadata=factor(rep(library_names[i], length(seu[[i]]$nCount_RNA)), levels=library_names), col.name="library")
}
# This is required for PrepSCTIntegration, an increase from the R default 512 to 5120 for this dataset
options(future.globals.maxSize=5120*1024^2)

seu_features = SelectIntegrationFeatures(object.list = seu, nfeatures = 3000)
seu = PrepSCTIntegration(object.list = seu, anchor.features = seu_features, verbose = FALSE)
seu = lapply(X = seu, FUN = RunPCA, verbose = FALSE, features = seu_features)
seu_anchors = FindIntegrationAnchors(object.list = seu, normalization.method = "SCT", anchor.features = seu_features, verbose = FALSE, reduction = "rpca")

  |                                                  | 0 % ~calculating  
  |+++++++++                                         | 17% ~40s          
  |+++++++++++++++++                                 | 33% ~27s          
  |+++++++++++++++++++++++++                         | 50% ~19s          
  |++++++++++++++++++++++++++++++++++                | 67% ~15s          
  |++++++++++++++++++++++++++++++++++++++++++        | 83% ~07s          
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=45s  
seu_integrated = IntegrateData(anchorset = seu_anchors, normalization.method = "SCT", verbose = FALSE)
seu_integrated@meta.data$library = factor(seu_integrated@meta.data$library, levels=library_names)
seu_integrated = RunPCA(seu_integrated, verbose = FALSE)

VlnPlot(seu_integrated, group.by="orig.ident", features = c("nFeature_RNA", "nCount_RNA", "percent.mito"), ncol = 3, pt.size=0.01)
Warning in FetchData(object = object, vars = features, slot = slot) :
  The following requested variables were not found: percent.mito

ElbowPlot(seu_integrated, ndims=50)

At 30 dimensions the tail is very flat already

seu_integrated = RunUMAP(seu_integrated, dims = 1:30)
Warning: The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session
23:39:04 UMAP embedding parameters a = 0.9922 b = 1.112
23:39:04 Read 13111 rows and found 30 numeric columns
23:39:04 Using Annoy for neighbor search, n_neighbors = 30
23:39:04 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
23:39:06 Writing NN index file to temp file /var/folders/bz/p63rv1754pv9v6rz33xgmy8sgyrrg3/T//Rtmpjo3Fsa/file57144845a637
23:39:06 Searching Annoy index using 1 thread, search_k = 3000
23:39:11 Annoy recall = 100%
23:39:16 Commencing smooth kNN distance calibration using 1 thread
23:39:19 Initializing from normalized Laplacian + noise
23:39:20 Commencing optimization for 200 epochs, with 566230 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
23:39:30 Optimization finished
UMAPPlot(seu_integrated)

p = UMAPPlot(seu_integrated,group.by="library")
p = theme_publication_plot(p, "Library")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_umap_overlay_library.pdf"),
                   base_height=4,
                   base_width=6)


FeaturePlot(seu_integrated, features=c("Krt14", "Tgm3", "Krt4", "Lor"))


VlnPlot(seu_integrated, group.by="orig.ident", features = "prop.mt", ncol = 1, pt.size=0.01)


FeaturePlot(seu_integrated, features=c("nFeature_RNA", "nCount_RNA", "prop.mt"))

Assign a celltype to all cells

Here we consider a number of markers with a threshold each to establish celltypes

celltypes = do.call(rbind, celltypes_all)
tpm = calc_tpm(seu_integrated@assays[["RNA"]]@counts, ref_file_mm10)

# get tpm for marker genes per cluster
seu_integrated = FindNeighbors(seu_integrated, dims = 1:30)
Computing nearest neighbor graph
Computing SNN
seu_integrated = FindClusters(object = seu_integrated, resolution = 0.6, verbose=FALSE)
markers_all_cells = FindAllMarkers(seu_integrated, only.pos = TRUE, min.pct = 0.1, thresh.use = 0.25, verbose = F)
For a more efficient implementation of the Wilcoxon Rank Sum Test,
(default method for FindMarkers) please install the limma package
--------------------------------------------
install.packages('BiocManager')
BiocManager::install('limma')
--------------------------------------------
After installation of limma, Seurat will automatically use the more 
efficient implementation (no further action necessary).
This message will be shown once per session
mdata = seu_integrated@meta.data
mdata$celltype = NA
inboth = intersect(gsub("-1", "", rownames(mdata)), celltypes$cell)
mdata$celltype[match(gsub("-1", "", rownames(mdata)), inboth)] = celltypes$classification[match(inboth, celltypes$cell)]
seu_integrated = AddMetaData(seu_integrated, mdata$celltype, "celltype")

UMAPPlot(seu_integrated, group.by="celltype")


cluster_classification = data.frame(clusterid=sort(unique(mdata$seurat_clusters)))

make_plot = function(mdata, tpm, gene_name) {
  plot_dat = data.frame(cell=rownames(mdata), cluster=mdata$seurat_clusters, tpm=tpm[gene_name,rownames(mdata)])
  plot_dat = plot_dat[is.finite(plot_dat$tpm),]
  plot_dat_clusters  = plot_dat %>% group_by(cluster) %>% summarise(n=n(), mean=mean(tpm), median=median(tpm))
  p2 = ggplot(plot_dat_clusters) + aes(x=cluster, y=median, label=cluster) + geom_text() + theme_cowplot() + ggtitle(gene_name)
  return(list(p=p2, plot_dat_clusters=plot_dat_clusters))
}

make_umap_plot = function(plot_dat, gene_name) {
  plot_dat[, gene_name] = log(plot_dat[, gene_name])
  gene_name_title = paste0(toupper(substr(gene_name, 1, 1)), substr(gene_name, 2, nchar(gene_name)))
  return(ggplot(plot_dat) + aes_string(x="UMAP_1", y="UMAP_2", colour=gene_name) + 
           geom_point(size=0.25) + scale_colour_gradient(low="grey", high="red") +
           theme_cowplot() + ggtitle(gene_name_title))
}

plot_data = as.data.frame(seu_integrated@reductions$umap@cell.embeddings)

# Keratinocytes
p1 = make_plot(mdata, tpm, "Krt14")
cluster_classification$krt14 = p1$plot_dat_clusters$median > 50
plot_data$krt14 = tpm["Krt14", rownames(plot_data)]

p2 = make_plot(mdata, tpm, "Tgm3")
cluster_classification$tgm3 = p2$plot_dat_clusters$median > 5
plot_data$tgm3 = tpm["Tgm3", rownames(plot_data)]

p3 = make_plot(mdata, tpm, "Lor")
cluster_classification$lor = p3$plot_dat_clusters$median > 50
plot_data$lor = tpm["Lor", rownames(plot_data)]

p4 = make_plot(mdata, tpm, "Krt4")
# cluster_classification$tgm3 = plot_dat_clusters$median > 10
plot_data$krt4 = tpm["Krt4", rownames(plot_data)]

plot_grid(plotlist=list(p1$p, p2$p, p3$p, p4$p), ncol=2)

p = plot_grid(plotlist=list(theme_publication_plot(make_umap_plot(plot_data, "krt14"), "log(TPM)"),
                        theme_publication_plot(make_umap_plot(plot_data, "tgm3"), "log(TPM)"),
                        theme_publication_plot(make_umap_plot(plot_data, "lor"), "log(TPM)"),
                        theme_publication_plot(make_umap_plot(plot_data, "krt4"), "log(TPM)")), ncol=2, nrow=2)
print(p)

plot_grid(plotlist=list(make_umap_plot(plot_data, "krt14"),
                        make_umap_plot(plot_data, "krt4")), ncol=2, nrow=2)

plot_grid(plotlist=list(make_umap_plot(plot_data, "krt14"),
                        make_umap_plot(plot_data, "tgm3")), ncol=2, nrow=2)

cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_markers_keratinocyte.pdf"),
                   base_height=4,
                   base_width=6)



# Fibroblasts and endothelial cells
p1 = make_plot(mdata, tpm, "Col1a1")
cluster_classification$col1a1 = p1$plot_dat_clusters$median > 10
plot_data$col1a1 = tpm["Col1a1", rownames(plot_data)]

p2 = make_plot(mdata, tpm, "Pecam1")
cluster_classification$pecam1 = p2$plot_dat_clusters$median > 2
plot_data$pcam1 = tpm["Pecam1", rownames(plot_data)]

plot_grid(plotlist=list(p1$p, p2$p), ncol=2)

p = plot_grid(plotlist=list(theme_publication_plot(make_umap_plot(plot_data, "col1a1"), "log(TPM)"),
                        theme_publication_plot(make_umap_plot(plot_data, "pcam1"), "log(TPM)")), ncol=2, nrow=2)
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_markers_fibroblast_endothelial.pdf"),
                   base_height=4,
                   base_width=6)



# Immune cells

# B-cell marker
p1 = make_plot(mdata, tpm, "Cd83")
cluster_classification$cd83 = p1$plot_dat_clusters$median > 1
plot_data$cd83 = tpm["Cd83", rownames(plot_data)]

p2 = make_plot(mdata, tpm, "Cd84")
cluster_classification$cd84 = p2$plot_dat_clusters$median > 1
plot_data$cd84 = tpm["Cd84", rownames(plot_data)]

p3 = make_plot(mdata, tpm, "Cd86")
cluster_classification$cd86 = p3$plot_dat_clusters$median > 0.4
plot_data$cd86 = tpm["Cd86", rownames(plot_data)]

plot_grid(plotlist=list(p1$p, p2$p, p3$p), ncol=2)

plot_grid(plotlist=list(make_umap_plot(plot_data, "cd83"),
                        make_umap_plot(plot_data, "cd84"),
                        make_umap_plot(plot_data, "cd86")), ncol=2, nrow=2)


# T-cells

# t-cell marker
p1 = make_plot(mdata, tpm, "Trbc2")
cluster_classification$trbc2 = p1$plot_dat_clusters$median > 50
plot_data$trbc2 = tpm["Trbc2", rownames(plot_data)]

# not specific
p2 = make_plot(mdata, tpm, "Cd52")
cluster_classification$cd52 = p2$plot_dat_clusters$median > 50
plot_data$cd52 = tpm["Cd52", rownames(plot_data)]

# t-cell marker
p3 = make_plot(mdata, tpm, "Ptprc")
cluster_classification$ptprc = p3$plot_dat_clusters$median > 50
plot_data$ptprc = tpm["Ptprc", rownames(plot_data)]

plot_grid(plotlist=list(p1$p, p2$p, p3$p), ncol=2)

p = plot_grid(plotlist=list(theme_publication_plot(make_umap_plot(plot_data, "ptprc"), "log(TPM)"),
                        theme_publication_plot(make_umap_plot(plot_data, "cd52"), "log(TPM)")), ncol=2, nrow=2)
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_markers_immune.pdf"),
                   base_height=4,
                   base_width=6)

plot <- DimPlot(object = seu_integrated, reduction = "umap")
LabelClusters(plot = plot, id = 'ident', size=6)

Now lets assign a celltype using the marker thresholds established above

cluster_classification$celltype = NA
cluster_classification$celltype[cluster_classification$krt14 | cluster_classification$tgm3 | cluster_classification$lor] = "keratinocyte"
cluster_classification$celltype[cluster_classification$col1a1] = "fibroblast"
cluster_classification$celltype[cluster_classification$pecam1] = "endothelial"
cluster_classification$celltype[cluster_classification$cd83 | cluster_classification$cd84 | cluster_classification$cd86 | cluster_classification$cd52 | cluster_classification$trbc2] = "immune"
cluster_classification_table = cluster_classification$celltype
names(cluster_classification_table) = as.character(cluster_classification$clusterid)
seu_integrated = AddMetaData(seu_integrated, cluster_classification_table[as.character(seu_integrated@meta.data$seurat_clusters)], "celltype")
UMAPPlot(seu_integrated, group.by="celltype")


plot_dat = as.data.frame(seu_integrated@reductions[["umap"]]@cell.embeddings)
plot_dat$celltype = seu_integrated@meta.data$celltype
plot_dat$celltype = unlist(lapply(plot_dat$celltype, function(x) { paste(toupper(substr(x, 1, 1)), substr(x, 2, nchar(x)), sep="") }))
plot_dat$celltype_fctr = factor(plot_dat$celltype, levels=c("Keratinocyte", "Fibroblast", "Immune", "Endothelial"))

A number of cells appear to be assigned to the wrong cluster, based on the UMAP space, set these to NA, just to be sure.

plot_data = as.data.frame(seu_integrated@reductions$umap@cell.embeddings)
plot_data$celltype = seu_integrated@meta.data$celltype
plot_data = plot_data[plot_data$celltype=="keratinocyte",]
plot_data$selection = plot_data$UMAP_1 < -5 | plot_data$UMAP_2 > 5
plot_data$selection_fctr = factor(plot_data$selection, levels=c(TRUE, FALSE))

mycolours = c("red", "grey")
names(mycolours) = c(TRUE, FALSE)

p = ggplot(plot_data) + aes_string(x="UMAP_1", y="UMAP_2", colour="selection_fctr") + 
           geom_point(size=0.25) + scale_colour_manual(values=mycolours) +
           theme_cowplot()
print(p)

seu_integrated@meta.data$celltype[rownames(seu_integrated@meta.data) %in% rownames(plot_data)[plot_data$selection]] = NA
UMAPPlot(seu_integrated, group.by="Phase")


plot_data = as.data.frame(seu_integrated@reductions$umap@cell.embeddings)
plot_data$celltype = seu_integrated@meta.data$celltype
plot_data$celltype = unlist(lapply(plot_data$celltype, function(x) { paste(toupper(substr(x, 1, 1)), substr(x, 2, nchar(x)), sep="") }))
plot_data$celltype_fctr = factor(plot_data$celltype, levels=c("Keratinocyte", "Fibroblast", "Immune", "Endothelial"))
mycolours = brewer.pal(4, "Dark2")
p = ggplot(plot_data) + 
  aes(x=UMAP_1, y=UMAP_2, colour=celltype_fctr) + 
  geom_point(size=0.2) + 
  scale_colour_manual(values=mycolours, na.value="grey") +
  xlab("UMAP 1") + ylab("UMAP 2")
p = theme_publication_plot(p, "Cell type")
print(p)

cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5d.pdf"),
                   base_height=4,
                   base_width=6)

Plot Notch1 expression per library

notch1_expr = data.frame(expression=tpm["Notch1", rownames(seu_integrated@meta.data)], library=seu_integrated@meta.data$library)
notch1_expr$library = factor(notch1_expr$library, levels=c("WT 1", "WT 2", "HOM KO 1", "HOM KO 2"))
p = ggplot(notch1_expr) + aes(x=library, y=expression, fill=library) + geom_boxplot() + 
  theme_cowplot() + xlab("Library") + ylab("TPM")
p = theme_publication_plot(p, "Library", legend_aes = 1)
print(p)

cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_notch1_expression_all_cells.pdf"),
                   base_height=4,
                   base_width=6)

Save

save(file=paste0(fileprefix, "notch1_batch_effect_with_filter_with_adjustment.RData"), seu_integrated, markers_all_cells)
metdata = seu_integrated@meta.data
metdata$cell = rownames(metdata)
write.table(metdata, file=paste0(fileprefix, "notch1_celltypes_and_metadata.txt"), quote=F, sep="\t", row.names=F)

Cell type counts per library

celltype_count = seu_integrated@meta.data[, c("orig.ident", "celltype")] %>% group_by(orig.ident, celltype) %>% summarise(n=n()) %>% spread(celltype, n)
`summarise()` has grouped output by 'orig.ident'. You can override using the `.groups` argument.
celltype_frac = celltype_count[, 2:ncol(celltype_count)] / rowSums(celltype_count[, 2:ncol(celltype_count)], na.rm=T)
celltype_frac = cbind(data.frame(library=c("WT 1", "WT 2", "HOM KO 1", "HOM KO 2")), celltype_frac)
celltype_frac
write.table(celltype_frac, file=paste0(fileprefix, "notch1_cell_type_counts_per_library.txt"), quote=F, sep="\t", row.names=F)


celltype_frac$library = factor(celltype_frac$library, levels=c("WT 1", "WT 2", "HOM KO 1", "HOM KO 2"))
cell_type_frac_wider = celltype_frac %>% pivot_longer(!library)
cell_type_frac_wider$name = unlist(lapply(cell_type_frac_wider$name, function(x) { paste(toupper(substr(x, 1, 1)), substr(x, 2, nchar(x)), sep="") }))
cell_type_frac_wider$name = factor(cell_type_frac_wider$name, levels=levels(plot_dat$celltype_fctr))
p = ggplot(cell_type_frac_wider) + 
  aes(x=library, y=value, fill=name) + 
  geom_bar(position="dodge", stat="identity") + 
  scale_fill_manual(values=mycolours, na.value="grey") +
  xlab("Library") + ylab("Proportion of cells per library")
p = theme_publication_plot(p, "Cell type")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5e_alt1.pdf"),
                   base_height=4,
                   base_width=6)


p = ggplot(cell_type_frac_wider) + 
  aes(x=library, y=value, fill=name) + 
  geom_bar(stat="identity", position = position_fill(reverse = TRUE)) + 
  scale_fill_manual(values=mycolours, na.value="grey") +
  xlab("Library") + ylab("Proportion of cells per library")
p = theme_publication_plot(p, "Cell type")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5e_alt2.pdf"),
                   base_height=4,
                   base_width=6)

Analysis keratinocytes

Rerun integration on keratinocytes only. Here we do not perform cell cycle adjustment, as the cell cycle is deeply connected to the tissue dynamics we’re interested in

keratinocyte_barcodes = rownames(seu_integrated@meta.data)[seu_integrated@meta.data$celltype=="keratinocyte" & !is.na(seu_integrated@meta.data$celltype)]

# Initial run found these cells as an outlier cluster and expressing fibroblast markers. Here these are also removed
misidentified_fibroblasts = read.table("20201211_fibroblast_identified_as_keratinocyte.txt", header=T, stringsAsFactors=F)
keratinocyte_barcodes = keratinocyte_barcodes[keratinocyte_barcodes %in% misidentified_fibroblasts$cell[!misidentified_fibroblasts$fibroblast_identified_as_keratinocyte]]

outlier_cells = read.table("20201211_notch1_cluster_assignments_to_remove_outlier_cells.txt", header=T, stringsAsFactors=F)
outlier_cells = outlier_cells[outlier_cells$is_outlier,]
keratinocyte_barcodes = keratinocyte_barcodes[!keratinocyte_barcodes %in% outlier_cells$cell]

seu_kera = list()
for (i in 1:length(expr_matrices)) {
  seu_kera[[i]] = CreateSeuratObject(counts = expr_matrices[[i]][, colnames(expr_matrices[[i]]) %in% keratinocyte_barcodes], min.cells = 30, min.features = 3000)
  
  mito.genes <- rownames(GetAssayData(object=seu_kera[[i]]))[grepl(pattern = "^MT-", x = toupper(rownames(GetAssayData(object=seu_kera[[i]]))))]
  percent.mito <- Matrix::colSums(GetAssayData(object=seu_kera[[i]], slot="counts")[mito.genes, ]) / Matrix::colSums(GetAssayData(object=seu_kera[[i]], slot="counts"))
  seu_kera[[i]] <- AddMetaData(object = seu_kera[[i]], metadata = percent.mito, col.name = "prop.mt")
  
  # Restrict proportion MT expression to remove unhappy cells
  seu_kera[[i]] = subset(x = seu_kera[[i]], subset = prop.mt > 0.03 & prop.mt < 0.1)
  
  # Remove potential doublets
  seu[[i]] = subset(x = seu[[i]], subset = nFeature_RNA < 6500 & nCount_RNA < 55000)
  
  seu_kera[[i]] = SCTransform(seu_kera[[i]], verbose = FALSE, vars.to.regress=c("prop.mt", "nFeature_RNA", "nCount_RNA"))
  seu_kera[[i]] = CellCycleScoring(seu_kera[[i]], s.features = cc.genes$s.genes, g2m.features = cc.genes$g2m.genes, assay = 'SCT', set.ident = TRUE)
  # Enable this when also adjusting for cell cycle
  # seu[[i]] = SCTransform(seu[[i]], verbose = FALSE, vars.to.regress=c("prop.mt", "nFeature_RNA", "nCount_RNA", "S.Score", "G2M.Score"))
  
  seu[[i]] = AddMetaData(object=seu[[i]], metadata=rep(library_names[i], length(seu[[i]]$nCount_RNA)), col.name="library")
}
# This is required for PrepSCTIntegration, an increase from the R default 512 to 5120 for this dataset
options(future.globals.maxSize=5120*1024^2)

seu_features = SelectIntegrationFeatures(object.list = seu_kera, nfeatures = 3000)
seu_kera = PrepSCTIntegration(object.list = seu_kera, anchor.features = seu_features, verbose = FALSE)
seu_kera = lapply(X = seu_kera, FUN = RunPCA, verbose = FALSE, features = seu_features)
seu_anchors = FindIntegrationAnchors(object.list = seu_kera, normalization.method = "SCT", anchor.features = seu_features, verbose = FALSE, reduction = "rpca")

  |                                                  | 0 % ~calculating  
  |+++++++++                                         | 17% ~27s          
  |+++++++++++++++++                                 | 33% ~18s          
  |+++++++++++++++++++++++++                         | 50% ~14s          
  |++++++++++++++++++++++++++++++++++                | 67% ~10s          
  |++++++++++++++++++++++++++++++++++++++++++        | 83% ~05s          
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=33s  
seu_kera_integrated = IntegrateData(anchorset = seu_anchors, normalization.method = "SCT", verbose = FALSE)
library_per_cell = factor(unlist(lapply(seu, function(x) x@meta.data$library)), levels=library_names)
names(library_per_cell) = unlist(lapply(seu, function(x) rownames(x@meta.data)))
seu_kera_integrated = AddMetaData(object=seu_kera_integrated, metadata=library_per_cell, col.name="library")
seu_kera_integrated = RunPCA(seu_kera_integrated, verbose = FALSE)

VlnPlot(seu_kera_integrated, group.by="orig.ident", features = c("nFeature_RNA", "nCount_RNA", "prop.mt"), ncol = 3, pt.size=0.01)

ElbowPlot(seu_kera_integrated, ndims=50)

This time we pick 10 dimensions, the point where the tail flattens off

UMAPPlot(seu_kera_integrated)
p = UMAPPlot(seu_kera_integrated,group.by="library")
p = theme_publication_plot(p, "Library") + ggtitle(NULL) + xlab("UMAP 1") + ylab("UMAP 2")
print(p)

cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_umap_keratinocytes_overlay_library.pdf"),
                   base_height=4,
                   base_width=6)



UMAPPlot(seu_kera_integrated,group.by="Phase")


plot_dat = as.data.frame(seu_kera_integrated@reductions[["umap"]]@cell.embeddings)
plot_dat$krt14 = log(tpm["Krt14", rownames(seu_kera_integrated@meta.data)])
plot_dat$tgm3 = log(tpm["Tgm3", rownames(seu_kera_integrated@meta.data)])
plot_dat$krt4 = log(tpm["Krt4", rownames(seu_kera_integrated@meta.data)])
plot_dat$lor = log(tpm["Lor", rownames(seu_kera_integrated@meta.data)])

p1 = theme_publication_plot(ggplot(plot_dat) + aes(x=UMAP_1, y=UMAP_2, colour=krt14) + geom_point(size=0.2) + xlab("UMAP 1") + ylab("UMAP 2") + theme_cowplot() + ggtitle("Krt14"), legend_title = "log(TPM)")
p2 = theme_publication_plot(ggplot(plot_dat) + aes(x=UMAP_1, y=UMAP_2, colour=tgm3) + geom_point(size=0.2) + xlab("UMAP 1") + ylab("UMAP 2") + theme_cowplot() + ggtitle("Tgm3"), legend_title = "log(TPM)")
p3 = theme_publication_plot(ggplot(plot_dat) + aes(x=UMAP_1, y=UMAP_2, colour=krt4) + geom_point(size=0.2) + xlab("UMAP 1") + ylab("UMAP 2") + theme_cowplot() + ggtitle("Krt4"), legend_title = "log(TPM)")
p4 = theme_publication_plot(ggplot(plot_dat) + aes(x=UMAP_1, y=UMAP_2, colour=lor) + geom_point(size=0.2) + xlab("UMAP 1") + ylab("UMAP 2") + theme_cowplot() + ggtitle("Lor"), legend_title = "log(TPM)")
p = plot_grid(p1, p2, p3, p4, ncol=2)
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_keratinocytes_marker_expression.pdf"),
                   base_height=4,
                   base_width=6)


VlnPlot(seu_kera_integrated, group.by="orig.ident", features = "prop.mt", ncol = 1, pt.size=0.01)


FeaturePlot(seu_kera_integrated, features=c("nFeature_RNA", "nCount_RNA", "prop.mt"))


plot_dat = as.data.frame(seu_kera_integrated@reductions$umap@cell.embeddings)
plot_dat$phase = seu_kera_integrated@meta.data$Phase
plot_dat$phase = factor(plot_dat$phase, levels=c("G1", "G2M", "S"))
# mycolours = brewer.pal(4, "Dark2")
p = ggplot(plot_dat) + 
  aes(x=UMAP_1, y=UMAP_2, colour=phase) + 
  geom_point(size=0.2) + 
  xlab("UMAP 1") + ylab("UMAP 2")
p = theme_publication_plot(p, "Cell type")
print(p)

cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5h.pdf"),
                   base_height=4,
                   base_width=6)


phase_count = seu_kera_integrated@meta.data %>% group_by(library, Phase) %>% summarise(n=n()) %>% mutate(frac = n/sum(n))
`summarise()` has grouped output by 'library'. You can override using the `.groups` argument.
phase_count$library = factor(phase_count$library, levels=c("WT 1", "WT 2", "HOM KO 1", "HOM KO 2"))
p = ggplot(phase_count) + 
  aes(x=library, y=frac, fill=Phase) + 
  geom_bar(position="dodge", stat="identity") + 
  xlab("Library") + ylab("Proportion of cells per library")
p = theme_publication_plot(p, "Cycle phase")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5i_alt1.pdf"),
                   base_height=4,
                   base_width=6)


p = ggplot(phase_count) + 
  aes(x=library, y=frac, fill=Phase) + 
  geom_bar(stat="identity", position = position_fill(reverse = TRUE)) + 
  xlab("Library") + ylab("Proportion of cells per library")
p = theme_publication_plot(p, "Cycle phase")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5i_alt2.pdf"),
                   base_height=4,
                   base_width=6)

Plot the cluster annotations in two ways - to match the heatmap further down.

p <- DimPlot(object = seu_kera_integrated, reduction = "umap")
p = LabelClusters(plot = p, id = 'ident', size=6)
p = p + xlab("UMAP 1") + ylab("UMAP 2")
p = theme_publication_plot(p, "Cluster") + ggtitle(NULL)
print(p)
# cowplot::save_plot(plot=p,
#                    filename = paste0(fileprefix, "cluster_annotations.pdf"),
#                    base_height=4,
#                    base_width=6)

seu_kera_integrated@meta.data$seurat_clusters_reordered = factor(as.numeric(as.character(seu_kera_integrated@meta.data$seurat_clusters)), levels=c(8,6,9,4,10,0,1,11,3,5,2,7))
p <- DimPlot(object = seu_kera_integrated, reduction = "umap", group.by = "seurat_clusters_reordered")
p = LabelClusters(plot = p, id = 'seurat_clusters_reordered', size=6)
p = p + xlab("UMAP 1") + ylab("UMAP 2")
p = theme_publication_plot(p, "Cluster") + ggtitle(NULL)
print(p)

# cowplot::save_plot(plot=p,
#                    filename = paste0(fileprefix, "cluster_annotations_alt.pdf"),
#                    base_height=4,
#                    base_width=6)
write.table(markers_keratinocytes, file=paste0(fileprefix, "signif_expr_markers_keratinocyte_analysis.txt"), quote=F, sep="\t", row.names=F)

Determine number of basal cells

# established to broadly capture the changeover point where Krt14 drops of and Tgm3 starts going up
slope = -1.5
intercept = -4.5

add_threshold = function(p, slope, intercept) {
  return(p + geom_abline(slope=slope, intercept=intercept, colour="black", linetype=2))
}

pc1 = as.data.frame(seu_kera_integrated@reductions[["umap"]]@cell.embeddings[, c(1, 2), drop=F])
pc1$library = as.character(seu_kera_integrated@meta.data$library[match(rownames(pc1), rownames(seu_kera_integrated@meta.data))])
pc1$selected = ifelse(pc1$UMAP_2 < (intercept+slope*pc1$UMAP_1), 0, 1)
pc1$selected = factor(pc1$selected, levels=c(1, 0))
pc1$layer = "Basal"
pc1$layer[pc1$selected=="0"] = "Suprabasal"
pc1$layer = factor(pc1$layer, levels=c("Basal", "Suprabasal"))

Plot the expression of a number of markers relative to the selected threshold to show we’ve broadly captured the crossover area

plot_dat = as.data.frame(seu_kera_integrated@reductions[["umap"]]@cell.embeddings)
plot_dat$krt14 = log(tpm["Krt14", rownames(seu_kera_integrated@meta.data)])
plot_dat$tgm3 = log(tpm["Tgm3", rownames(seu_kera_integrated@meta.data)])
plot_dat$krt4 = log(tpm["Krt4", rownames(seu_kera_integrated@meta.data)])
plot_dat$lor = log(tpm["Lor", rownames(seu_kera_integrated@meta.data)])

p1 = theme_publication_plot(ggplot(plot_dat) + aes(x=UMAP_1, y=UMAP_2, colour=krt14) + geom_point(size=0.2) + xlab("UMAP 1") + ylab("UMAP 2") + theme_cowplot() + ggtitle("Krt14"), legend_title = "log(TPM)")
p2 = theme_publication_plot(ggplot(plot_dat) + aes(x=UMAP_1, y=UMAP_2, colour=tgm3) + geom_point(size=0.2) + xlab("UMAP 1") + ylab("UMAP 2") + theme_cowplot() + ggtitle("Tgm3"), legend_title = "log(TPM)")
p3 = theme_publication_plot(ggplot(plot_dat) + aes(x=UMAP_1, y=UMAP_2, colour=krt4) + geom_point(size=0.2) + xlab("UMAP 1") + ylab("UMAP 2") + theme_cowplot() + ggtitle("Krt4"), legend_title = "log(TPM)")
p4 = theme_publication_plot(ggplot(plot_dat) + aes(x=UMAP_1, y=UMAP_2, colour=lor) + geom_point(size=0.2) + xlab("UMAP 1") + ylab("UMAP 2") + theme_cowplot() + ggtitle("Lor"), legend_title = "log(TPM)")
p = plot_grid(add_threshold(p1, slope, intercept), 
              add_threshold(p2, slope, intercept), 
              add_threshold(p3, slope, intercept), 
              add_threshold(p4, slope, intercept), ncol=2)
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_basal_cell_threshold_vs_markers.pdf"),
                   base_height=4,
                   base_width=6)

Plot the threshold per library to show which cells have been marked as basal cells

mycolours = c("red", "grey")
p1 = ggplot(pc1[pc1$library=="WT 1",]) +
  aes(x=UMAP_1, y=UMAP_2, colour=layer) +
  geom_point(size=0.2) +
  scale_colour_manual(values=mycolours, na.value="grey") +
  xlab("UMAP 1") + ylab("UMAP 2") + ggtitle("WT 1") + theme_cowplot() + theme(legend.position="none")

p2 = ggplot(pc1[pc1$library=="WT 2",]) +
  aes(x=UMAP_1, y=UMAP_2, colour=layer) +
  geom_point(size=0.2) +
  scale_colour_manual(values=mycolours, na.value="grey") +
  xlab("UMAP 1") + ylab("UMAP 2") + ggtitle("WT 2") + theme_cowplot() + theme(legend.position="none") 

p3 = ggplot(pc1[pc1$library=="HOM KO 1",]) +
  aes(x=UMAP_1, y=UMAP_2, colour=layer) +
  geom_point(size=0.2) +
  scale_colour_manual(values=mycolours, na.value="grey") +
  xlab("UMAP 1") + ylab("UMAP 2") + ggtitle("HOM KO 1") + theme_cowplot() + theme(legend.position="none")

p4 = ggplot(pc1[pc1$library=="HOM KO 2",]) +
  aes(x=UMAP_1, y=UMAP_2, colour=layer) +
  geom_point(size=0.2) +
  scale_colour_manual(values=mycolours, na.value="grey") +
  xlab("UMAP 1") + ylab("UMAP 2") + ggtitle("HOM KO 2") + theme_cowplot() + theme(legend.position="none")

p = plot_grid(add_threshold(p1, slope, intercept), 
              add_threshold(p2, slope, intercept), 
              add_threshold(p3, slope, intercept), 
              add_threshold(p4, slope, intercept))
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_basal_cell_classification_map.pdf"),
                   base_height=4,
                   base_width=6)

Summarise the calls into a table

basal_cell_count = pc1 %>% group_by(selected, library) %>% summarise(n=n()) %>% pivot_wider(names_from=selected, values_from=n)
`summarise()` has grouped output by 'selected'. You can override using the `.groups` argument.
colnames(basal_cell_count)[2:3] = c("num_basal", "num_suprabasal")
basal_cell_count[, c("frac_basal", "frac_suprabasal")] = basal_cell_count[, 2:3] / rowSums(basal_cell_count[, 2:3])
print(basal_cell_count)
write.table(basal_cell_count, file=paste0(fileprefix, "notch1_fraction_basal_cells_umap_threshold.txt"), quote=F, sep="\t", row.names=F)

Make the summary figures for the paper


mycolours = c("red", "grey")
p = ggplot(pc1) + 
  aes(x=UMAP_1, y=UMAP_2, colour=layer) + 
  geom_point(size=0.2) + 
  scale_colour_manual(values=mycolours, na.value="grey") +
  xlab("UMAP 1") + ylab("UMAP 2")
p = theme_publication_plot(p, "Layer")
print(p)

cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5f.pdf"),
                   base_height=4,
                   base_width=6)


basal_cell_count = basal_cell_count[,c("library", "frac_basal", "frac_suprabasal")]
basal_cell_count$library = factor(basal_cell_count$library, levels=c("WT 1", "WT 2", "HOM KO 1", "HOM KO 2"))
colnames(basal_cell_count)[2:3] = c("Basal", "Suprabasal")
p = ggplot(basal_cell_count %>% pivot_longer(!library)) + 
  aes(x=library, y=value, fill=name) + 
  geom_bar(position="dodge", stat="identity") + 
  scale_fill_manual(values=mycolours, na.value="grey") +
  xlab("Library") + ylab("Proportion of cells per library")
p = theme_publication_plot(p, "Layer")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5g_alt1.pdf"),
                   base_height=4,
                   base_width=6)


p = ggplot(basal_cell_count %>% pivot_longer(!library)) + 
  aes(x=library, y=value, fill=name) +
  geom_bar(stat="identity", position = position_fill(reverse = TRUE)) + 
  scale_fill_manual(values=mycolours, na.value="grey") +
  xlab("Library") + ylab("Proportion of cells per library")
p = theme_publication_plot(p, "Layer")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "figure5g_alt2.pdf"),
                   base_height=4,
                   base_width=6)

Save the outcome

# save the Krt4 estimates
metadat = seu_kera_integrated@meta.data
metadat$cell = rownames(metadat)

metadat = metadat[, c("cell", "orig.ident", "Phase", "prop.mt", "nCount_RNA", "nFeature_RNA")]
colnames(metadat)[2] = "library"

metadat$is_basal = pc1$layer=="Basal"
write.table(metadat, file=paste0(fileprefix, "notch1_basal_suprabasal_calls_umap_threshold.txt"), quote=F, sep="\t", row.names=F)

Plot percentage basal cells per cell cycle phase

phase_count = seu_kera_integrated@meta.data[metadat$is_basal,] %>% group_by(library, Phase) %>% summarise(n=n()) %>% mutate(frac = n/sum(n))
`summarise()` has grouped output by 'library'. You can override using the `.groups` argument.
phase_count$library = factor(phase_count$library, levels=c("WT 1", "WT 2", "HOM KO 1", "HOM KO 2"))

p = ggplot(phase_count) + 
  aes(x=library, y=frac, fill=Phase) + 
  geom_bar(stat="identity", position = position_fill(reverse = TRUE)) + 
  xlab("Library") + ylab("Proportion of cells per library")
p = theme_publication_plot(p, "Cycle phase")
print(p)


p = ggplot(phase_count) + 
  aes(x=library, y=n, fill=Phase) + 
  geom_bar(stat="identity", position = "dodge") + 
  xlab("Library") + ylab("Number of cells per library")
p = theme_publication_plot(p, "Cycle phase")
print(p)

save(file=paste0(fileprefix, "notch1_keratinocytes_batch_effect_with_filter_with_adjustment.RData"), seu_kera_integrated, markers_keratinocytes, cluster_lib_count)

Additional plots

Downsampled plot showing equal cells per library

counts_per_library = table(seu_integrated@meta.data$library)
set.seed(123)
selected_cells = rownames(seu_integrated@meta.data)[seu_integrated@meta.data$library=="WT 1"][sample(1:counts_per_library["WT 1"], 1500)]

selected_cells = c(selected_cells, rownames(seu_integrated@meta.data)[seu_integrated@meta.data$library=="WT 2"][sample(1:counts_per_library["WT 2"], 1500)])

selected_cells = c(selected_cells, rownames(seu_integrated@meta.data)[seu_integrated@meta.data$library=="HOM KO 1"][sample(1:counts_per_library["HOM KO 1"], 1500)])

selected_cells = c(selected_cells, rownames(seu_integrated@meta.data)[seu_integrated@meta.data$library=="HOM KO 2"][sample(1:counts_per_library["HOM KO 2"], 1500)])



seu_integrated_temp = subset(seu_integrated, cells=selected_cells)

p = UMAPPlot(seu_integrated_temp,group.by="library")
p = theme_publication_plot(p, "Library") + ggtitle(NULL) + xlab("UMAP 1") + ylab("UMAP 2")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_umap_overlay_library_downsampled1500.pdf"),
                   base_height=4,
                   base_width=6)


counts_per_library = table(seu_kera_integrated@meta.data$library)
set.seed(123)
selected_cells = rownames(seu_kera_integrated@meta.data)[seu_kera_integrated@meta.data$library=="WT 1"][sample(1:counts_per_library["WT 1"], 1400)]

selected_cells = c(selected_cells, rownames(seu_kera_integrated@meta.data)[seu_kera_integrated@meta.data$library=="WT 2"][sample(1:counts_per_library["WT 2"], 1400)])

selected_cells = c(selected_cells, rownames(seu_kera_integrated@meta.data)[seu_kera_integrated@meta.data$library=="HOM KO 1"][sample(1:counts_per_library["HOM KO 1"], 1400)])

selected_cells = c(selected_cells, rownames(seu_kera_integrated@meta.data)[seu_kera_integrated@meta.data$library=="HOM KO 2"][sample(1:counts_per_library["HOM KO 2"], 1400)])


seu_integrated_temp = subset(seu_kera_integrated, cells=selected_cells)

p = UMAPPlot(seu_integrated_temp,group.by="library")
p = theme_publication_plot(p, "Library") + ggtitle(NULL) + xlab("UMAP 1") + ylab("UMAP 2")
print(p)
cowplot::save_plot(plot=p,
                   filename = paste0(fileprefix, "supplfig_umap_overlay_library_keratinocytes_downsampled1400.pdf"),
                   base_height=4,
                   base_width=6)

# load("20210717_notch1_batch_effect_with_filter_with_adjustment.RData")

Plot heatmap with selected marker genes from McGin et al expression per cluster

load(paste0(fileprefix, "notch1_keratinocytes_batch_effect_with_filter_with_adjustment.RData"))
basal_markers = c("Cdh3", "Itgb1", "Krt15", "Krt14", "Krt5", "Col17a1", "Sox2", "Trp63")
cycle_markers = c("Gmnn", "Mcm6", "Mcm2", "Cdt1", "Pcna", "Ccne1", "E2f1", "Ccne2", "Cdc6", "Aurkb", "Top2a", "Ccnb2", "Bub1", "Ube2c", "Aurka", "Kif23", "Ccnb1", "Mki67", "Mad2l1", "Birc5")
diff_markers = c("Krt13", "Klf4", "Tgm3", "Sbsn", "Grhl3", "Krt4", "Notch3")
seu_kera_integrated@meta.data$seurat_clusters_reordered = factor(as.numeric(as.character(seu_kera_integrated@meta.data$seurat_clusters)), levels=c(8,6,9,4,10,0,1,11,3,5,2,7))
# p = DoHeatmap(seu_kera_integrated, features = c(basal_markers, cycle_markers, diff_markers), group.by="seurat_clusters_reordered") + NoLegend()
# print(p)
# cowplot::save_plot(plot=p,
#                     filename = "figure_heatmap_all_libraries.pdf",
#                     base_height=4,
#                     base_width=8)
p = DoHeatmap(seu_kera_integrated, 
          cells = rownames(seu_kera_integrated@meta.data)[seu_kera_integrated@meta.data$orig.ident %in% c("c1", "c2")],
          features = c(basal_markers, cycle_markers, diff_markers), group.by="seurat_clusters_reordered") + NoLegend()
Warning in DoHeatmap(seu_kera_integrated, cells = rownames(seu_kera_integrated@meta.data)[seu_kera_integrated@meta.data$orig.ident %in%  :
  The following features were omitted as they were not found in the scale.data slot for the integrated assay: Cdc6
print(p)
cowplot::save_plot(plot=p,
                    filename = "figure_heatmap_control_libraries.pdf",
                    base_height=4,
                    base_width=8)

p = DoHeatmap(seu_kera_integrated, 
          cells = rownames(seu_kera_integrated@meta.data)[seu_kera_integrated@meta.data$orig.ident %in% c("s1", "s2")],
          features = c(basal_markers, cycle_markers, diff_markers), group.by="seurat_clusters_reordered") + NoLegend()
Warning in DoHeatmap(seu_kera_integrated, cells = rownames(seu_kera_integrated@meta.data)[seu_kera_integrated@meta.data$orig.ident %in%  :
  The following features were omitted as they were not found in the scale.data slot for the integrated assay: Cdc6
print(p)
cowplot::save_plot(plot=p,
                    filename = "figure_heatmap_ko_libraries.pdf",
                    base_height=4,
                    base_width=8)

Save a table with counts per cluster per library

cluster_counts = list()
for (cluster in unique(seu_kera_integrated@meta.data$seurat_clusters)) {
  res = seu_kera_integrated@meta.data[seu_kera_integrated@meta.data$seurat_clusters==cluster,] %>% group_by(library) %>% summarise(n=n())
  res$cluster = cluster
  cluster_counts[[cluster]] = res
}
cluster_counts = do.call(rbind, cluster_counts)
write.table(cluster_counts, file=paste0(fileprefix, "_cluster_library_cell_counts.txt"), quote=F, sep="\t", row.names=F)
LS0tCnRpdGxlOiAiTm90Y2gxIC0gYW5hbHlzaXMgdjEuMiIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB5ZXMKLS0tCgojIEZ1bmN0aW9ucyBhbmQgaW1wb3J0cwpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9CmZpbGVwcmVmaXggPSAiMjAyMTA3MTdfIgoKcmVmX2ZpbGVfbW0xMCA9ICJ+L0RvY3VtZW50cy9yZWZlcmVuY2VfZGF0YS9tb3VzZS9lbnNlbWJsL2Vuc2VtYmxfZ2VuZV9sZW5ndGhfbW0xMC50eHQuZ3oiCgojIFRoaXMgaXMgcmVxdWlyZWQgdG8gc2V0IHRoZSBmb250cyBvZiB0aGUgcGFwZXIgcGxvdHMgdG8gQXJpYWwKbGlicmFyeShleHRyYWZvbnQpCmZvbnRfaW1wb3J0KHByb21wdD1GQUxTRSkKbG9hZGZvbnRzKCkKCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShzY2F0ZXIpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShTb3VwWCkKCmNhbGNfdHBtID0gZnVuY3Rpb24oZGF0LCByZWZfZmlsZSkgewogIG1tX2Fubm90ID0gcmVhZC50YWJsZShyZWZfZmlsZSwgaGVhZGVyPVQsIHN0cmluZ3NBc0ZhY3RvcnM9Riwgc2VwPSJcdCIpCiAgZWZmX2xlbmd0aCA9IG1tX2Fubm90JGVmZl9sZW5ndGhbbWF0Y2gocm93bmFtZXMoZGF0KSwgbW1fYW5ub3QkbWdpX3N5bWJvbCldCiAgZWZmX2xlbmd0aFshaXMuZmluaXRlKGVmZl9sZW5ndGgpXSA9IDEKICAKICBzY2UgPSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXM9bGlzdChjb3VudHM9YXMubWF0cml4KGRhdCksIGxvZ2NvdW50cz1sb2cyKGFzLm1hdHJpeChkYXQpKzEpKSkKICB0cG0gPSBjYWxjdWxhdGVUUE0oc2NlLCBlZmZfbGVuZ3RoKQogIHJldHVybih0cG0pCn0KCnRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QgPSBmdW5jdGlvbihwLCBsZWdlbmRfdGl0bGUsIGxlZ2VuZF9hZXM9NCkgewogIHAgPSBwICsKICB0aGVtZV9jb3dwbG90KCkgKyAKICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5PSJBcmlhbCIsIGNvbG91cj0iYmxhY2siLHNpemU9MTAsZmFjZT0icGxhaW4iKSwKICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseT0iQXJpYWwiLCBjb2xvdXI9ImJsYWNrIixzaXplPTEyLGZhY2U9InBsYWluIiksCiAgICAgICAgICAgICAgICAgICAgICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KGZhbWlseT0iQXJpYWwiLCBjb2xvdXI9ImJsYWNrIixzaXplPTEyLGZhY2U9InBsYWluIiwgYW5nbGU9OTApLAogICAgICAgICAgICAgICAgICAgICAgIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChmYW1pbHk9IkFyaWFsIiwgY29sb3VyPSJibGFjayIsc2l6ZT0xMixmYWNlPSJwbGFpbiIsIGFuZ2xlPTM2MCksCiAgICAgICAgICAgICAgICAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBwYW5lbC5zcGFjaW5nLnggPSB1bml0KDEsICJtbSIpLAogICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLnNwYWNpbmcueSA9IHVuaXQoMywgIm1tIiksCiAgICAgICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYW1pbHk9IkFyaWFsIiwgY29sb3VyPSJibGFjayIsc2l6ZT0xMixmYWNlPSJwbGFpbiIsaGp1c3QgPSAwLjUpKQogIGlmICghaXMubnVsbChsZWdlbmRfdGl0bGUpKSB7CiAgICAgcCA9IHAgKyBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZCh0aXRsZT1sZWdlbmRfdGl0bGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT1sZWdlbmRfYWVzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSBndWlkZV9sZWdlbmQodGl0bGU9bGVnZW5kX3RpdGxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9bGVnZW5kX2FlcykpKQogIH0KICByZXR1cm4ocCkKfQpgYGAKCiMgTG9hZCB0aGUgZGF0YQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNhbXBsZW5hbWVzID0gYygiRUFfV1RfMSIsICJFQV9XVF8yIiwgIkVBX05PVENIMV9IT01fMSIsICJFQV9OT1RDSDFfSE9NXzIiKQpsaWJyYXJ5X25hbWVzID0gYygiV1QgMSIsICJXVCAyIiwgIkhPTSBLTyAxIiwgIkhPTSBLTyAyIikKcHJlZml4ZXMgPSBjKCJjMV8iLCAiYzJfIiwgInMxXyIsICJzMl8iKQpleHByX21hdHJpY2VzID0gbGlzdCgpCiMgY2VsbHR5cGVzX2FsbCA9IGxpc3QoKQpmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlbmFtZXMpKSB7CiAgcHJlZml4ID0gcHJlZml4ZXNbaV0KICBpbl9wYXRoID0gZmlsZS5wYXRoKCIuLi9jZWxscmFuZ2VyLyIsIHNhbXBsZW5hbWVzW2ldKQogIHNjID0gbG9hZDEwWChpbl9wYXRoKQogIAogIG91dCA9IHNjJHRvYwogIGNvbG5hbWVzKG91dCkgPSBwYXN0ZTAocHJlZml4LCBjb2xuYW1lcyhvdXQpKQoKICBleHByX21hdHJpY2VzW1tsZW5ndGgoZXhwcl9tYXRyaWNlcykrMV1dID0gb3V0CiAgCiAgIyBUaGlzIGxvYWRzIGEgcHJldmlvdXMgc2V0IG9mIGNlbGwgdHlwZSBhc3NpZ25tZW50cyB0aGF0IHdhcyB1c2VkIHRvIGNvbXBhcmUgdGhlIG5ldyBzZXR1cAogICMgY2VsbHR5cGVzID0gcmVhZC50YWJsZShmaWxlLnBhdGgoIi4uL2lkZW50aWZ5X2NlbGx0eXBlcyIsIHNhbXBsZW5hbWVzW2ldLCAiY2VsbHR5cGVzX3Blcl9jZWxsLnR4dCIpLCBoZWFkZXI9VCwgc3RyaW5nc0FzRmFjdG9ycz1GKQogICMgY2VsbHR5cGVzJGNlbGwgPSBwYXN0ZTAocHJlZml4LCBjZWxsdHlwZXMkY2VsbCkKICAjIGNlbGx0eXBlc19hbGxbW2xlbmd0aChjZWxsdHlwZXNfYWxsKSsxXV0gPSBjZWxsdHlwZXMKfQpgYGAKCiMgUmVhZCBpbiBzb21lIENlbGxSYW5nZXIgc3RhdHMKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpzdGF0cyA9IGxpc3QoKQpmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlbmFtZXMpKSB7CiAgc3RhdHNfc2FtcGxlID0gcmVhZC50YWJsZShmaWxlLnBhdGgoIi4uL2NlbGxyYW5nZXIvIiwgc2FtcGxlbmFtZXNbaV0sICJtZXRyaWNzX3N1bW1hcnkuY3N2IiksIHNlcD0iLCIsIGhlYWRlcj1ULCBzdHJpbmdzQXNGYWN0b3JzPUYpCiAgc3RhdHNfc2FtcGxlJGxpYnJhcnkgPSBzYW1wbGVuYW1lc1tpXQogIHN0YXRzW1tzYW1wbGVuYW1lc1tpXV1dID0gc3RhdHNfc2FtcGxlCn0Kc3RhdHMgPSBkby5jYWxsKHJiaW5kLCBzdGF0cykKcHJpbnQoc3RhdHMpCmBgYAoKCiMgRXhwbG9yZSB0byBpZGVudGlmeSBmaWx0ZXJzCgpIZXJlIHdlIHBsb3QgYSBzdW1tYXJ5IG9mIGdlbmVyYWwgUUMgbWV0cmljcyB0byBpZGVudGlmeSB1bmhhcHB5IGNlbGxzIHRvIGJlIGZpbHRlcmVkCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbkZlYXR1cmVfUk5BX21pbiA9IDI1MDAKbkZlYXR1cmVfUk5BX21heCA9IDY1MDAKbkNvdW50X1JOQV9tYXggPSA1NTAwMApwcm9wLm10X21pbiA9IDAuMDMKcHJvcC5tdF9tYXggPSAwLjEKCnNldSA9IGxpc3QoKQpmb3IgKGkgaW4gMTpsZW5ndGgoZXhwcl9tYXRyaWNlcykpIHsKICBzZXVbW2ldXSA9IENyZWF0ZVNldXJhdE9iamVjdChjb3VudHMgPSBleHByX21hdHJpY2VzW1tpXV0sIG1pbi5jZWxscyA9IDAsIG1pbi5mZWF0dXJlcyA9IDApCiAgCiAgbWl0by5nZW5lcyA9IHJvd25hbWVzKEdldEFzc2F5RGF0YShvYmplY3Q9c2V1W1tpXV0pKVtncmVwbChwYXR0ZXJuID0gIl5NVC0iLCB4ID0gdG91cHBlcihyb3duYW1lcyhHZXRBc3NheURhdGEob2JqZWN0PXNldVtbaV1dKSkpKV0KICBwZXJjZW50Lm1pdG8gPSBNYXRyaXg6OmNvbFN1bXMoR2V0QXNzYXlEYXRhKG9iamVjdD1zZXVbW2ldXSwgc2xvdD0iY291bnRzIilbbWl0by5nZW5lcywgXSkgLyBNYXRyaXg6OmNvbFN1bXMoR2V0QXNzYXlEYXRhKG9iamVjdD1zZXVbW2ldXSwgc2xvdD0iY291bnRzIikpCiAgc2V1W1tpXV0gPSBBZGRNZXRhRGF0YShvYmplY3QgPSBzZXVbW2ldXSwgbWV0YWRhdGEgPSBwZXJjZW50Lm1pdG8sIGNvbC5uYW1lID0gInByb3AubXQiKQogIAogICMgcGxvdCBwcm9wLm10IHZzIG5GZWF0dXJlX1JOQSBhbmQgdnMgbkNvdW50X1JOQQogIHAxID0gZ2dwbG90KHNldVtbaV1dQG1ldGEuZGF0YSkgKyBhZXMoeD1wcm9wLm10LCB5PW5GZWF0dXJlX1JOQSkgKyBnZW9tX3BvaW50KHNpemU9MC4xLCBjb2xvdXI9InJlZCIpICsgc2NhbGVfeV9sb2cxMCgpICsgdGhlbWVfY293cGxvdCgpICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWMocHJvcC5tdF9taW4sIHByb3AubXRfbWF4KSwgbGluZXR5cGU9MikgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9YyhuRmVhdHVyZV9STkFfbWluLCBuRmVhdHVyZV9STkFfbWF4KSwgbGluZXR5cGU9MikKICAKICBwMiA9IGdncGxvdChzZXVbW2ldXUBtZXRhLmRhdGEpICsgYWVzKHg9cHJvcC5tdCwgeT1uQ291bnRfUk5BKSArIGdlb21fcG9pbnQoc2l6ZT0wLjEsIGNvbG91cj0iYmx1ZSIpICsgc2NhbGVfeV9sb2cxMCgpICsgdGhlbWVfY293cGxvdCgpICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWMocHJvcC5tdF9taW4sIHByb3AubXRfbWF4KSwgbGluZXR5cGU9MikgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9YyhuQ291bnRfUk5BX21heCksIGxpbmV0eXBlPTIpCiAgCiAgcHJpbnQocGxvdF9ncmlkKHAxLCBwMikpCiAgCiAgcDEgPSBWbG5QbG90KHNldVtbaV1dLCBncm91cC5ieT0ib3JpZy5pZGVudCIsIGZlYXR1cmVzID0gYyggIm5GZWF0dXJlX1JOQSIpLCBuY29sID0gMSwgcHQuc2l6ZT0wKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBjKG5GZWF0dXJlX1JOQV9taW4sIG5GZWF0dXJlX1JOQV9tYXgpLCBsaW5ldHlwZT0yKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCiAgcDIgPSBWbG5QbG90KHNldVtbaV1dLCBncm91cC5ieT0ib3JpZy5pZGVudCIsIGZlYXR1cmVzID0gYyggIm5Db3VudF9STkEiKSwgbmNvbCA9IDEsIHB0LnNpemU9MCkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYyhuQ291bnRfUk5BX21heCksIGxpbmV0eXBlPTIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikKICBwMyA9IFZsblBsb3Qoc2V1W1tpXV0sIGdyb3VwLmJ5PSJvcmlnLmlkZW50IiwgZmVhdHVyZXMgPSBjKCAicHJvcC5tdCIpLCBuY29sID0gMSwgcHQuc2l6ZT0wKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYyhwcm9wLm10X21heCwgcHJvcC5tdF9taW4pLCBsaW5ldHlwZT0yKQogIAogICMgaWYgKGdyZXBsKCJLTyIsIHNhbXBsZW5hbWVzW2ldLCBmaXhlZD1UKSkgewogICMgICBwMyA9IHAzICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYygwLjEsIDAuMDAyKSwgbGluZXR5cGU9MikKICAjIH0gZWxzZSB7CiAgIyAgIHAzID0gcDMgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBjKDAuMSwgMC4wMSksIGxpbmV0eXBlPTIpCiAgIyB9CiAgCiAgcHJpbnQocGxvdF9ncmlkKHAxLCBwMiwgcDMsIG5jb2w9MykpCn0KYGBgCgojIEFsbCBjZWxscwojIyBBcHBseSBiYXRjaCBjb3JyZWN0aW9uIC0gTVQgZmlsdGVycyBhbmQgYWRqdXN0IGZvciBNVCwgbkdlbmVzIGFuZCBjZWxsIGN5Y2xlCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0Kc2V1ID0gbGlzdCgpCmZvciAoaSBpbiAxOmxlbmd0aChleHByX21hdHJpY2VzKSkgewogIHNldVtbaV1dID0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IGV4cHJfbWF0cmljZXNbW2ldXSwgbWluLmNlbGxzID0gMzAsIG1pbi5mZWF0dXJlcyA9IDI1MDApCiAgCiAgbWl0by5nZW5lcyA9IHJvd25hbWVzKEdldEFzc2F5RGF0YShvYmplY3Q9c2V1W1tpXV0pKVtncmVwbChwYXR0ZXJuID0gIl5NVC0iLCB4ID0gdG91cHBlcihyb3duYW1lcyhHZXRBc3NheURhdGEob2JqZWN0PXNldVtbaV1dKSkpKV0KICBwZXJjZW50Lm1pdG8gPSBNYXRyaXg6OmNvbFN1bXMoR2V0QXNzYXlEYXRhKG9iamVjdD1zZXVbW2ldXSwgc2xvdD0iY291bnRzIilbbWl0by5nZW5lcywgXSkgLyBNYXRyaXg6OmNvbFN1bXMoR2V0QXNzYXlEYXRhKG9iamVjdD1zZXVbW2ldXSwgc2xvdD0iY291bnRzIikpCiAgc2V1W1tpXV0gPSBBZGRNZXRhRGF0YShvYmplY3QgPSBzZXVbW2ldXSwgbWV0YWRhdGEgPSBwZXJjZW50Lm1pdG8sIGNvbC5uYW1lID0gInByb3AubXQiKQogIAogICMgUmVzdHJpY3QgcHJvcG9ydGlvbiBNVCBleHByZXNzaW9uIHRvIHJlbW92ZSB1bmhhcHB5IGNlbGxzCiAgc2V1W1tpXV0gPSBzdWJzZXQoeCA9IHNldVtbaV1dLCBzdWJzZXQgPSBwcm9wLm10ID4gMC4wMyAmIHByb3AubXQgPCAwLjEpCiAgCiAgIyBSZW1vdmUgcG90ZW50aWFsIGRvdWJsZXRzCiAgc2V1W1tpXV0gPSBzdWJzZXQoeCA9IHNldVtbaV1dLCBzdWJzZXQgPSBuRmVhdHVyZV9STkEgPCA2NTAwICYgbkNvdW50X1JOQSA8IDU1MDAwKQogIAogIHNldVtbaV1dID0gU0NUcmFuc2Zvcm0oc2V1W1tpXV0sIHZlcmJvc2UgPSBGQUxTRSwgdmFycy50by5yZWdyZXNzPWMoInByb3AubXQiLCAibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiKSkKICBzZXVbW2ldXSA9IENlbGxDeWNsZVNjb3Jpbmcoc2V1W1tpXV0sIHMuZmVhdHVyZXMgPSBjYy5nZW5lcyRzLmdlbmVzLCBnMm0uZmVhdHVyZXMgPSBjYy5nZW5lcyRnMm0uZ2VuZXMsIGFzc2F5ID0gJ1NDVCcsIHNldC5pZGVudCA9IFRSVUUpCiAgIyBFbmFibGUgdGhpcyB3aGVuIGFsc28gYWRqdXN0aW5nIGZvciBjZWxsIGN5Y2xlCiAgc2V1W1tpXV0gPSBTQ1RyYW5zZm9ybShzZXVbW2ldXSwgdmVyYm9zZSA9IEZBTFNFLCB2YXJzLnRvLnJlZ3Jlc3M9YygicHJvcC5tdCIsICJuRmVhdHVyZV9STkEiLCAibkNvdW50X1JOQSIsICJTLlNjb3JlIiwgIkcyTS5TY29yZSIpKQogIAogIHNldVtbaV1dID0gQWRkTWV0YURhdGEob2JqZWN0PXNldVtbaV1dLCBtZXRhZGF0YT1mYWN0b3IocmVwKGxpYnJhcnlfbmFtZXNbaV0sIGxlbmd0aChzZXVbW2ldXSRuQ291bnRfUk5BKSksIGxldmVscz1saWJyYXJ5X25hbWVzKSwgY29sLm5hbWU9ImxpYnJhcnkiKQp9CmBgYAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQojIFRoaXMgaXMgcmVxdWlyZWQgZm9yIFByZXBTQ1RJbnRlZ3JhdGlvbiwgYW4gaW5jcmVhc2UgZnJvbSB0aGUgUiBkZWZhdWx0IDUxMiB0byA1MTIwIGZvciB0aGlzIGRhdGFzZXQKb3B0aW9ucyhmdXR1cmUuZ2xvYmFscy5tYXhTaXplPTUxMjAqMTAyNF4yKQoKc2V1X2ZlYXR1cmVzID0gU2VsZWN0SW50ZWdyYXRpb25GZWF0dXJlcyhvYmplY3QubGlzdCA9IHNldSwgbmZlYXR1cmVzID0gMzAwMCkKc2V1ID0gUHJlcFNDVEludGVncmF0aW9uKG9iamVjdC5saXN0ID0gc2V1LCBhbmNob3IuZmVhdHVyZXMgPSBzZXVfZmVhdHVyZXMsIHZlcmJvc2UgPSBGQUxTRSkKc2V1ID0gbGFwcGx5KFggPSBzZXUsIEZVTiA9IFJ1blBDQSwgdmVyYm9zZSA9IEZBTFNFLCBmZWF0dXJlcyA9IHNldV9mZWF0dXJlcykKc2V1X2FuY2hvcnMgPSBGaW5kSW50ZWdyYXRpb25BbmNob3JzKG9iamVjdC5saXN0ID0gc2V1LCBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJTQ1QiLCBhbmNob3IuZmVhdHVyZXMgPSBzZXVfZmVhdHVyZXMsIHZlcmJvc2UgPSBGQUxTRSwgcmVkdWN0aW9uID0gInJwY2EiKQpzZXVfaW50ZWdyYXRlZCA9IEludGVncmF0ZURhdGEoYW5jaG9yc2V0ID0gc2V1X2FuY2hvcnMsIG5vcm1hbGl6YXRpb24ubWV0aG9kID0gIlNDVCIsIHZlcmJvc2UgPSBGQUxTRSkKc2V1X2ludGVncmF0ZWRAbWV0YS5kYXRhJGxpYnJhcnkgPSBmYWN0b3Ioc2V1X2ludGVncmF0ZWRAbWV0YS5kYXRhJGxpYnJhcnksIGxldmVscz1saWJyYXJ5X25hbWVzKQpgYGAKCmBgYHtyfQpzZXVfaW50ZWdyYXRlZCA9IFJ1blBDQShzZXVfaW50ZWdyYXRlZCwgdmVyYm9zZSA9IEZBTFNFKQoKVmxuUGxvdChzZXVfaW50ZWdyYXRlZCwgZ3JvdXAuYnk9Im9yaWcuaWRlbnQiLCBmZWF0dXJlcyA9IGMoIm5GZWF0dXJlX1JOQSIsICJuQ291bnRfUk5BIiwgInBlcmNlbnQubWl0byIpLCBuY29sID0gMywgcHQuc2l6ZT0wLjAxKQpFbGJvd1Bsb3Qoc2V1X2ludGVncmF0ZWQsIG5kaW1zPTUwKQpgYGAKQXQgMzAgZGltZW5zaW9ucyB0aGUgdGFpbCBpcyB2ZXJ5IGZsYXQgYWxyZWFkeQpgYGB7cn0Kc2V1X2ludGVncmF0ZWQgPSBSdW5VTUFQKHNldV9pbnRlZ3JhdGVkLCBkaW1zID0gMTozMCkKVU1BUFBsb3Qoc2V1X2ludGVncmF0ZWQpCnAgPSBVTUFQUGxvdChzZXVfaW50ZWdyYXRlZCxncm91cC5ieT0ibGlicmFyeSIpCnAgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KHAsICJMaWJyYXJ5IikKcHJpbnQocCkKY293cGxvdDo6c2F2ZV9wbG90KHBsb3Q9cCwKICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gcGFzdGUwKGZpbGVwcmVmaXgsICJzdXBwbGZpZ191bWFwX292ZXJsYXlfbGlicmFyeS5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCgpGZWF0dXJlUGxvdChzZXVfaW50ZWdyYXRlZCwgZmVhdHVyZXM9YygiS3J0MTQiLCAiVGdtMyIsICJLcnQ0IiwgIkxvciIpKQoKVmxuUGxvdChzZXVfaW50ZWdyYXRlZCwgZ3JvdXAuYnk9Im9yaWcuaWRlbnQiLCBmZWF0dXJlcyA9ICJwcm9wLm10IiwgbmNvbCA9IDEsIHB0LnNpemU9MC4wMSkKCkZlYXR1cmVQbG90KHNldV9pbnRlZ3JhdGVkLCBmZWF0dXJlcz1jKCJuRmVhdHVyZV9STkEiLCAibkNvdW50X1JOQSIsICJwcm9wLm10IikpCmBgYAoKCiMjIyBBc3NpZ24gYSBjZWxsdHlwZSB0byBhbGwgY2VsbHMKCkhlcmUgd2UgY29uc2lkZXIgYSBudW1iZXIgb2YgbWFya2VycyB3aXRoIGEgdGhyZXNob2xkIGVhY2ggdG8gZXN0YWJsaXNoIGNlbGx0eXBlcwpgYGB7cn0KIyBjZWxsdHlwZXMgPSBkby5jYWxsKHJiaW5kLCBjZWxsdHlwZXNfYWxsKQp0cG0gPSBjYWxjX3RwbShzZXVfaW50ZWdyYXRlZEBhc3NheXNbWyJSTkEiXV1AY291bnRzLCByZWZfZmlsZV9tbTEwKQoKIyBnZXQgdHBtIGZvciBtYXJrZXIgZ2VuZXMgcGVyIGNsdXN0ZXIKc2V1X2ludGVncmF0ZWQgPSBGaW5kTmVpZ2hib3JzKHNldV9pbnRlZ3JhdGVkLCBkaW1zID0gMTozMCkKc2V1X2ludGVncmF0ZWQgPSBGaW5kQ2x1c3RlcnMob2JqZWN0ID0gc2V1X2ludGVncmF0ZWQsIHJlc29sdXRpb24gPSAwLjYsIHZlcmJvc2U9RkFMU0UpCm1hcmtlcnNfYWxsX2NlbGxzID0gRmluZEFsbE1hcmtlcnMoc2V1X2ludGVncmF0ZWQsIG9ubHkucG9zID0gVFJVRSwgbWluLnBjdCA9IDAuMSwgdGhyZXNoLnVzZSA9IDAuMjUsIHZlcmJvc2UgPSBGKQoKbWRhdGEgPSBzZXVfaW50ZWdyYXRlZEBtZXRhLmRhdGEKIyBPdmVybGF5IGEgcHJldmlvdXMgc2V0IG9mIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyB0byBjb21wYXJlCiMgbWRhdGEkY2VsbHR5cGUgPSBOQQojIGluYm90aCA9IGludGVyc2VjdChnc3ViKCItMSIsICIiLCByb3duYW1lcyhtZGF0YSkpLCBjZWxsdHlwZXMkY2VsbCkKIyBtZGF0YSRjZWxsdHlwZVttYXRjaChnc3ViKCItMSIsICIiLCByb3duYW1lcyhtZGF0YSkpLCBpbmJvdGgpXSA9IGNlbGx0eXBlcyRjbGFzc2lmaWNhdGlvblttYXRjaChpbmJvdGgsIGNlbGx0eXBlcyRjZWxsKV0KIyBzZXVfaW50ZWdyYXRlZCA9IEFkZE1ldGFEYXRhKHNldV9pbnRlZ3JhdGVkLCBtZGF0YSRjZWxsdHlwZSwgImNlbGx0eXBlIikKCiMgVU1BUFBsb3Qoc2V1X2ludGVncmF0ZWQsIGdyb3VwLmJ5PSJjZWxsdHlwZSIpCgpjbHVzdGVyX2NsYXNzaWZpY2F0aW9uID0gZGF0YS5mcmFtZShjbHVzdGVyaWQ9c29ydCh1bmlxdWUobWRhdGEkc2V1cmF0X2NsdXN0ZXJzKSkpCgptYWtlX3Bsb3QgPSBmdW5jdGlvbihtZGF0YSwgdHBtLCBnZW5lX25hbWUpIHsKICBwbG90X2RhdCA9IGRhdGEuZnJhbWUoY2VsbD1yb3duYW1lcyhtZGF0YSksIGNsdXN0ZXI9bWRhdGEkc2V1cmF0X2NsdXN0ZXJzLCB0cG09dHBtW2dlbmVfbmFtZSxyb3duYW1lcyhtZGF0YSldKQogIHBsb3RfZGF0ID0gcGxvdF9kYXRbaXMuZmluaXRlKHBsb3RfZGF0JHRwbSksXQogIHBsb3RfZGF0X2NsdXN0ZXJzICA9IHBsb3RfZGF0ICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgc3VtbWFyaXNlKG49bigpLCBtZWFuPW1lYW4odHBtKSwgbWVkaWFuPW1lZGlhbih0cG0pKQogIHAyID0gZ2dwbG90KHBsb3RfZGF0X2NsdXN0ZXJzKSArIGFlcyh4PWNsdXN0ZXIsIHk9bWVkaWFuLCBsYWJlbD1jbHVzdGVyKSArIGdlb21fdGV4dCgpICsgdGhlbWVfY293cGxvdCgpICsgZ2d0aXRsZShnZW5lX25hbWUpCiAgcmV0dXJuKGxpc3QocD1wMiwgcGxvdF9kYXRfY2x1c3RlcnM9cGxvdF9kYXRfY2x1c3RlcnMpKQp9CgptYWtlX3VtYXBfcGxvdCA9IGZ1bmN0aW9uKHBsb3RfZGF0LCBnZW5lX25hbWUpIHsKICBwbG90X2RhdFssIGdlbmVfbmFtZV0gPSBsb2cocGxvdF9kYXRbLCBnZW5lX25hbWVdKQogIGdlbmVfbmFtZV90aXRsZSA9IHBhc3RlMCh0b3VwcGVyKHN1YnN0cihnZW5lX25hbWUsIDEsIDEpKSwgc3Vic3RyKGdlbmVfbmFtZSwgMiwgbmNoYXIoZ2VuZV9uYW1lKSkpCiAgcmV0dXJuKGdncGxvdChwbG90X2RhdCkgKyBhZXNfc3RyaW5nKHg9IlVNQVBfMSIsIHk9IlVNQVBfMiIsIGNvbG91cj1nZW5lX25hbWUpICsgCiAgICAgICAgICAgZ2VvbV9wb2ludChzaXplPTAuMjUpICsgc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdz0iZ3JleSIsIGhpZ2g9InJlZCIpICsKICAgICAgICAgICB0aGVtZV9jb3dwbG90KCkgKyBnZ3RpdGxlKGdlbmVfbmFtZV90aXRsZSkpCn0KCnBsb3RfZGF0YSA9IGFzLmRhdGEuZnJhbWUoc2V1X2ludGVncmF0ZWRAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncykKCiMgS2VyYXRpbm9jeXRlcwpwMSA9IG1ha2VfcGxvdChtZGF0YSwgdHBtLCAiS3J0MTQiKQpjbHVzdGVyX2NsYXNzaWZpY2F0aW9uJGtydDE0ID0gcDEkcGxvdF9kYXRfY2x1c3RlcnMkbWVkaWFuID4gNTAKcGxvdF9kYXRhJGtydDE0ID0gdHBtWyJLcnQxNCIsIHJvd25hbWVzKHBsb3RfZGF0YSldCgpwMiA9IG1ha2VfcGxvdChtZGF0YSwgdHBtLCAiVGdtMyIpCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kdGdtMyA9IHAyJHBsb3RfZGF0X2NsdXN0ZXJzJG1lZGlhbiA+IDUKcGxvdF9kYXRhJHRnbTMgPSB0cG1bIlRnbTMiLCByb3duYW1lcyhwbG90X2RhdGEpXQoKcDMgPSBtYWtlX3Bsb3QobWRhdGEsIHRwbSwgIkxvciIpCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kbG9yID0gcDMkcGxvdF9kYXRfY2x1c3RlcnMkbWVkaWFuID4gNTAKcGxvdF9kYXRhJGxvciA9IHRwbVsiTG9yIiwgcm93bmFtZXMocGxvdF9kYXRhKV0KCnA0ID0gbWFrZV9wbG90KG1kYXRhLCB0cG0sICJLcnQ0IikKcGxvdF9kYXRhJGtydDQgPSB0cG1bIktydDQiLCByb3duYW1lcyhwbG90X2RhdGEpXQoKcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocDEkcCwgcDIkcCwgcDMkcCwgcDQkcCksIG5jb2w9MikKcCA9IHBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QobWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAia3J0MTQiKSwgImxvZyhUUE0pIiksCiAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QobWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAidGdtMyIpLCAibG9nKFRQTSkiKSwKICAgICAgICAgICAgICAgICAgICAgICAgdGhlbWVfcHVibGljYXRpb25fcGxvdChtYWtlX3VtYXBfcGxvdChwbG90X2RhdGEsICJsb3IiKSwgImxvZyhUUE0pIiksCiAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QobWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAia3J0NCIpLCAibG9nKFRQTSkiKSksIG5jb2w9MiwgbnJvdz0yKQpwcmludChwKQpwbG90X2dyaWQocGxvdGxpc3Q9bGlzdChtYWtlX3VtYXBfcGxvdChwbG90X2RhdGEsICJrcnQxNCIpLAogICAgICAgICAgICAgICAgICAgICAgICBtYWtlX3VtYXBfcGxvdChwbG90X2RhdGEsICJrcnQ0IikpLCBuY29sPTIsIG5yb3c9MikKcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QobWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAia3J0MTQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgbWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAidGdtMyIpKSwgbmNvbD0yLCBucm93PTIpCgpjb3dwbG90OjpzYXZlX3Bsb3QocGxvdD1wLAogICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSBwYXN0ZTAoZmlsZXByZWZpeCwgInN1cHBsZmlnX21hcmtlcnNfa2VyYXRpbm9jeXRlLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKCgojIEZpYnJvYmxhc3RzIGFuZCBlbmRvdGhlbGlhbCBjZWxscwoKcDEgPSBtYWtlX3Bsb3QobWRhdGEsIHRwbSwgIkNvbDFhMSIpCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY29sMWExID0gcDEkcGxvdF9kYXRfY2x1c3RlcnMkbWVkaWFuID4gMTAKcGxvdF9kYXRhJGNvbDFhMSA9IHRwbVsiQ29sMWExIiwgcm93bmFtZXMocGxvdF9kYXRhKV0KCnAyID0gbWFrZV9wbG90KG1kYXRhLCB0cG0sICJQZWNhbTEiKQpjbHVzdGVyX2NsYXNzaWZpY2F0aW9uJHBlY2FtMSA9IHAyJHBsb3RfZGF0X2NsdXN0ZXJzJG1lZGlhbiA+IDIKcGxvdF9kYXRhJHBjYW0xID0gdHBtWyJQZWNhbTEiLCByb3duYW1lcyhwbG90X2RhdGEpXQoKcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocDEkcCwgcDIkcCksIG5jb2w9MikKcCA9IHBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QobWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAiY29sMWExIiksICJsb2coVFBNKSIpLAogICAgICAgICAgICAgICAgICAgICAgICB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KG1ha2VfdW1hcF9wbG90KHBsb3RfZGF0YSwgInBjYW0xIiksICJsb2coVFBNKSIpKSwgbmNvbD0yLCBucm93PTIpCnByaW50KHApCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlMChmaWxlcHJlZml4LCAic3VwcGxmaWdfbWFya2Vyc19maWJyb2JsYXN0X2VuZG90aGVsaWFsLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKCgojIEltbXVuZSBjZWxscwoKIyBCLWNlbGwgbWFya2VyCnAxID0gbWFrZV9wbG90KG1kYXRhLCB0cG0sICJDZDgzIikKY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbiRjZDgzID0gcDEkcGxvdF9kYXRfY2x1c3RlcnMkbWVkaWFuID4gMQpwbG90X2RhdGEkY2Q4MyA9IHRwbVsiQ2Q4MyIsIHJvd25hbWVzKHBsb3RfZGF0YSldCgpwMiA9IG1ha2VfcGxvdChtZGF0YSwgdHBtLCAiQ2Q4NCIpCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2Q4NCA9IHAyJHBsb3RfZGF0X2NsdXN0ZXJzJG1lZGlhbiA+IDEKcGxvdF9kYXRhJGNkODQgPSB0cG1bIkNkODQiLCByb3duYW1lcyhwbG90X2RhdGEpXQoKcDMgPSBtYWtlX3Bsb3QobWRhdGEsIHRwbSwgIkNkODYiKQpjbHVzdGVyX2NsYXNzaWZpY2F0aW9uJGNkODYgPSBwMyRwbG90X2RhdF9jbHVzdGVycyRtZWRpYW4gPiAwLjQKcGxvdF9kYXRhJGNkODYgPSB0cG1bIkNkODYiLCByb3duYW1lcyhwbG90X2RhdGEpXQoKcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocDEkcCwgcDIkcCwgcDMkcCksIG5jb2w9MikKcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QobWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAiY2Q4MyIpLAogICAgICAgICAgICAgICAgICAgICAgICBtYWtlX3VtYXBfcGxvdChwbG90X2RhdGEsICJjZDg0IiksCiAgICAgICAgICAgICAgICAgICAgICAgIG1ha2VfdW1hcF9wbG90KHBsb3RfZGF0YSwgImNkODYiKSksIG5jb2w9MiwgbnJvdz0yKQoKIyB0LWNlbGwgbWFya2VyCnAxID0gbWFrZV9wbG90KG1kYXRhLCB0cG0sICJUcmJjMiIpCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kdHJiYzIgPSBwMSRwbG90X2RhdF9jbHVzdGVycyRtZWRpYW4gPiA1MApwbG90X2RhdGEkdHJiYzIgPSB0cG1bIlRyYmMyIiwgcm93bmFtZXMocGxvdF9kYXRhKV0KCiMgbm90IHNwZWNpZmljCnAyID0gbWFrZV9wbG90KG1kYXRhLCB0cG0sICJDZDUyIikKY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbiRjZDUyID0gcDIkcGxvdF9kYXRfY2x1c3RlcnMkbWVkaWFuID4gNTAKcGxvdF9kYXRhJGNkNTIgPSB0cG1bIkNkNTIiLCByb3duYW1lcyhwbG90X2RhdGEpXQoKIyB0LWNlbGwgbWFya2VyCnAzID0gbWFrZV9wbG90KG1kYXRhLCB0cG0sICJQdHByYyIpCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kcHRwcmMgPSBwMyRwbG90X2RhdF9jbHVzdGVycyRtZWRpYW4gPiAwLjI1CnBsb3RfZGF0YSRwdHByYyA9IHRwbVsiUHRwcmMiLCByb3duYW1lcyhwbG90X2RhdGEpXQoKcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocDEkcCwgcDIkcCwgcDMkcCksIG5jb2w9MikKcCA9IHBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QobWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAicHRwcmMiKSwgImxvZyhUUE0pIiksCiAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QobWFrZV91bWFwX3Bsb3QocGxvdF9kYXRhLCAiY2Q1MiIpLCAibG9nKFRQTSkiKSksIG5jb2w9MiwgbnJvdz0yKQpwcmludChwKQpjb3dwbG90OjpzYXZlX3Bsb3QocGxvdD1wLAogICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSBwYXN0ZTAoZmlsZXByZWZpeCwgInN1cHBsZmlnX21hcmtlcnNfaW1tdW5lLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKYGBgCgpgYGB7cn0KcGxvdCA8LSBEaW1QbG90KG9iamVjdCA9IHNldV9pbnRlZ3JhdGVkLCByZWR1Y3Rpb24gPSAidW1hcCIpCkxhYmVsQ2x1c3RlcnMocGxvdCA9IHBsb3QsIGlkID0gJ2lkZW50Jywgc2l6ZT02KQpgYGAKCk5vdyBsZXRzIGFzc2lnbiBhIGNlbGx0eXBlIHVzaW5nIHRoZSBtYXJrZXIgdGhyZXNob2xkcyBlc3RhYmxpc2hlZCBhYm92ZQpgYGB7cn0KY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbiRjZWxsdHlwZSA9IE5BCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2VsbHR5cGVbY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbiRrcnQxNCB8IGNsdXN0ZXJfY2xhc3NpZmljYXRpb24kdGdtMyB8IGNsdXN0ZXJfY2xhc3NpZmljYXRpb24kbG9yXSA9ICJrZXJhdGlub2N5dGUiCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2VsbHR5cGVbY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbiRjb2wxYTFdID0gImZpYnJvYmxhc3QiCmNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2VsbHR5cGVbY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbiRwZWNhbTFdID0gImVuZG90aGVsaWFsIgpjbHVzdGVyX2NsYXNzaWZpY2F0aW9uJGNlbGx0eXBlW2NsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2Q4MyB8IGNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2Q4NCB8IGNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2Q4NiB8IGNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2Q1MiB8IGNsdXN0ZXJfY2xhc3NpZmljYXRpb24kcHRwcmNdID0gImltbXVuZSIKY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbl90YWJsZSA9IGNsdXN0ZXJfY2xhc3NpZmljYXRpb24kY2VsbHR5cGUKbmFtZXMoY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbl90YWJsZSkgPSBhcy5jaGFyYWN0ZXIoY2x1c3Rlcl9jbGFzc2lmaWNhdGlvbiRjbHVzdGVyaWQpCnNldV9pbnRlZ3JhdGVkID0gQWRkTWV0YURhdGEoc2V1X2ludGVncmF0ZWQsIGNsdXN0ZXJfY2xhc3NpZmljYXRpb25fdGFibGVbYXMuY2hhcmFjdGVyKHNldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnMpXSwgImNlbGx0eXBlIikKVU1BUFBsb3Qoc2V1X2ludGVncmF0ZWQsIGdyb3VwLmJ5PSJjZWxsdHlwZSIpCgpwbG90X2RhdCA9IGFzLmRhdGEuZnJhbWUoc2V1X2ludGVncmF0ZWRAcmVkdWN0aW9uc1tbInVtYXAiXV1AY2VsbC5lbWJlZGRpbmdzKQpwbG90X2RhdCRjZWxsdHlwZSA9IHNldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRjZWxsdHlwZQpwbG90X2RhdCRjZWxsdHlwZSA9IHVubGlzdChsYXBwbHkocGxvdF9kYXQkY2VsbHR5cGUsIGZ1bmN0aW9uKHgpIHsgcGFzdGUodG91cHBlcihzdWJzdHIoeCwgMSwgMSkpLCBzdWJzdHIoeCwgMiwgbmNoYXIoeCkpLCBzZXA9IiIpIH0pKQpwbG90X2RhdCRjZWxsdHlwZV9mY3RyID0gZmFjdG9yKHBsb3RfZGF0JGNlbGx0eXBlLCBsZXZlbHM9YygiS2VyYXRpbm9jeXRlIiwgIkZpYnJvYmxhc3QiLCAiSW1tdW5lIiwgIkVuZG90aGVsaWFsIikpCmBgYAoKQSBudW1iZXIgb2YgY2VsbHMgYXBwZWFyIHRvIGJlIGFzc2lnbmVkIHRvIHRoZSB3cm9uZyBjbHVzdGVyLCBiYXNlZCBvbiB0aGUgVU1BUCBzcGFjZSwgc2V0IHRoZXNlIHRvIE5BLCBqdXN0IHRvIGJlIHN1cmUuCmBgYHtyfQpwbG90X2RhdGEgPSBhcy5kYXRhLmZyYW1lKHNldV9pbnRlZ3JhdGVkQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MpCnBsb3RfZGF0YSRjZWxsdHlwZSA9IHNldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRjZWxsdHlwZQpwbG90X2RhdGEgPSBwbG90X2RhdGFbcGxvdF9kYXRhJGNlbGx0eXBlPT0ia2VyYXRpbm9jeXRlIixdCnBsb3RfZGF0YSRzZWxlY3Rpb24gPSBwbG90X2RhdGEkVU1BUF8xIDwgLTUgfCBwbG90X2RhdGEkVU1BUF8yID4gNQpwbG90X2RhdGEkc2VsZWN0aW9uX2ZjdHIgPSBmYWN0b3IocGxvdF9kYXRhJHNlbGVjdGlvbiwgbGV2ZWxzPWMoVFJVRSwgRkFMU0UpKQoKbXljb2xvdXJzID0gYygicmVkIiwgImdyZXkiKQpuYW1lcyhteWNvbG91cnMpID0gYyhUUlVFLCBGQUxTRSkKCnAgPSBnZ3Bsb3QocGxvdF9kYXRhKSArIGFlc19zdHJpbmcoeD0iVU1BUF8xIiwgeT0iVU1BUF8yIiwgY29sb3VyPSJzZWxlY3Rpb25fZmN0ciIpICsgCiAgICAgICAgICAgZ2VvbV9wb2ludChzaXplPTAuMjUpICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9bXljb2xvdXJzKSArCiAgICAgICAgICAgdGhlbWVfY293cGxvdCgpCnByaW50KHApCnNldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRjZWxsdHlwZVtyb3duYW1lcyhzZXVfaW50ZWdyYXRlZEBtZXRhLmRhdGEpICVpbiUgcm93bmFtZXMocGxvdF9kYXRhKVtwbG90X2RhdGEkc2VsZWN0aW9uXV0gPSBOQQpVTUFQUGxvdChzZXVfaW50ZWdyYXRlZCwgZ3JvdXAuYnk9IlBoYXNlIikKCnBsb3RfZGF0YSA9IGFzLmRhdGEuZnJhbWUoc2V1X2ludGVncmF0ZWRAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncykKcGxvdF9kYXRhJGNlbGx0eXBlID0gc2V1X2ludGVncmF0ZWRAbWV0YS5kYXRhJGNlbGx0eXBlCnBsb3RfZGF0YSRjZWxsdHlwZSA9IHVubGlzdChsYXBwbHkocGxvdF9kYXRhJGNlbGx0eXBlLCBmdW5jdGlvbih4KSB7IHBhc3RlKHRvdXBwZXIoc3Vic3RyKHgsIDEsIDEpKSwgc3Vic3RyKHgsIDIsIG5jaGFyKHgpKSwgc2VwPSIiKSB9KSkKcGxvdF9kYXRhJGNlbGx0eXBlX2ZjdHIgPSBmYWN0b3IocGxvdF9kYXRhJGNlbGx0eXBlLCBsZXZlbHM9YygiS2VyYXRpbm9jeXRlIiwgIkZpYnJvYmxhc3QiLCAiSW1tdW5lIiwgIkVuZG90aGVsaWFsIikpCm15Y29sb3VycyA9IGJyZXdlci5wYWwoNCwgIkRhcmsyIikKcCA9IGdncGxvdChwbG90X2RhdGEpICsgCiAgYWVzKHg9VU1BUF8xLCB5PVVNQVBfMiwgY29sb3VyPWNlbGx0eXBlX2ZjdHIpICsgCiAgZ2VvbV9wb2ludChzaXplPTAuMikgKyAKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1teWNvbG91cnMsIG5hLnZhbHVlPSJncmV5IikgKwogIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkNlbGwgdHlwZSIpCnByaW50KHApCgpjb3dwbG90OjpzYXZlX3Bsb3QocGxvdD1wLAogICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSBwYXN0ZTAoZmlsZXByZWZpeCwgImZpZ3VyZTVkLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKYGBgClBsb3QgTm90Y2gxIGV4cHJlc3Npb24gcGVyIGxpYnJhcnkKYGBge3J9Cm5vdGNoMV9leHByID0gZGF0YS5mcmFtZShleHByZXNzaW9uPXRwbVsiTm90Y2gxIiwgcm93bmFtZXMoc2V1X2ludGVncmF0ZWRAbWV0YS5kYXRhKV0sIGxpYnJhcnk9c2V1X2ludGVncmF0ZWRAbWV0YS5kYXRhJGxpYnJhcnkpCm5vdGNoMV9leHByJGxpYnJhcnkgPSBmYWN0b3Iobm90Y2gxX2V4cHIkbGlicmFyeSwgbGV2ZWxzPWMoIldUIDEiLCAiV1QgMiIsICJIT00gS08gMSIsICJIT00gS08gMiIpKQpwID0gZ2dwbG90KG5vdGNoMV9leHByKSArIGFlcyh4PWxpYnJhcnksIHk9ZXhwcmVzc2lvbiwgZmlsbD1saWJyYXJ5KSArIGdlb21fYm94cGxvdCgpICsgCiAgdGhlbWVfY293cGxvdCgpICsgeGxhYigiTGlicmFyeSIpICsgeWxhYigiVFBNIikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkxpYnJhcnkiLCBsZWdlbmRfYWVzID0gMSkKcHJpbnQocCkKCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlMChmaWxlcHJlZml4LCAic3VwcGxmaWdfbm90Y2gxX2V4cHJlc3Npb25fYWxsX2NlbGxzLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKYGBgCiMjIFNhdmUKYGBge3J9CnNhdmUoZmlsZT1wYXN0ZTAoZmlsZXByZWZpeCwgIm5vdGNoMV9iYXRjaF9lZmZlY3Rfd2l0aF9maWx0ZXJfd2l0aF9hZGp1c3RtZW50LlJEYXRhIiksIHNldV9pbnRlZ3JhdGVkLCBtYXJrZXJzX2FsbF9jZWxscykKbWV0ZGF0YSA9IHNldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YQptZXRkYXRhJGNlbGwgPSByb3duYW1lcyhtZXRkYXRhKQp3cml0ZS50YWJsZShtZXRkYXRhLCBmaWxlPXBhc3RlMChmaWxlcHJlZml4LCAibm90Y2gxX2NlbGx0eXBlc19hbmRfbWV0YWRhdGEudHh0IiksIHF1b3RlPUYsIHNlcD0iXHQiLCByb3cubmFtZXM9RikKYGBgCgojIyBDZWxsIHR5cGUgY291bnRzIHBlciBsaWJyYXJ5CgpgYGB7cn0KY2VsbHR5cGVfY291bnQgPSBzZXVfaW50ZWdyYXRlZEBtZXRhLmRhdGFbLCBjKCJvcmlnLmlkZW50IiwgImNlbGx0eXBlIildICU+JSBncm91cF9ieShvcmlnLmlkZW50LCBjZWxsdHlwZSkgJT4lIHN1bW1hcmlzZShuPW4oKSkgJT4lIHNwcmVhZChjZWxsdHlwZSwgbikKY2VsbHR5cGVfZnJhYyA9IGNlbGx0eXBlX2NvdW50WywgMjpuY29sKGNlbGx0eXBlX2NvdW50KV0gLyByb3dTdW1zKGNlbGx0eXBlX2NvdW50WywgMjpuY29sKGNlbGx0eXBlX2NvdW50KV0sIG5hLnJtPVQpCmNlbGx0eXBlX2ZyYWMgPSBjYmluZChkYXRhLmZyYW1lKGxpYnJhcnk9YygiV1QgMSIsICJXVCAyIiwgIkhPTSBLTyAxIiwgIkhPTSBLTyAyIikpLCBjZWxsdHlwZV9mcmFjKQpjZWxsdHlwZV9mcmFjCndyaXRlLnRhYmxlKGNlbGx0eXBlX2ZyYWMsIGZpbGU9cGFzdGUwKGZpbGVwcmVmaXgsICJub3RjaDFfY2VsbF90eXBlX2NvdW50c19wZXJfbGlicmFyeS50eHQiKSwgcXVvdGU9Riwgc2VwPSJcdCIsIHJvdy5uYW1lcz1GKQoKCmNlbGx0eXBlX2ZyYWMkbGlicmFyeSA9IGZhY3RvcihjZWxsdHlwZV9mcmFjJGxpYnJhcnksIGxldmVscz1jKCJXVCAxIiwgIldUIDIiLCAiSE9NIEtPIDEiLCAiSE9NIEtPIDIiKSkKY2VsbF90eXBlX2ZyYWNfd2lkZXIgPSBjZWxsdHlwZV9mcmFjICU+JSBwaXZvdF9sb25nZXIoIWxpYnJhcnkpCmNlbGxfdHlwZV9mcmFjX3dpZGVyJG5hbWUgPSB1bmxpc3QobGFwcGx5KGNlbGxfdHlwZV9mcmFjX3dpZGVyJG5hbWUsIGZ1bmN0aW9uKHgpIHsgcGFzdGUodG91cHBlcihzdWJzdHIoeCwgMSwgMSkpLCBzdWJzdHIoeCwgMiwgbmNoYXIoeCkpLCBzZXA9IiIpIH0pKQpjZWxsX3R5cGVfZnJhY193aWRlciRuYW1lID0gZmFjdG9yKGNlbGxfdHlwZV9mcmFjX3dpZGVyJG5hbWUsIGxldmVscz1sZXZlbHMocGxvdF9kYXQkY2VsbHR5cGVfZmN0cikpCnAgPSBnZ3Bsb3QoY2VsbF90eXBlX2ZyYWNfd2lkZXIpICsgCiAgYWVzKHg9bGlicmFyeSwgeT12YWx1ZSwgZmlsbD1uYW1lKSArIAogIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9bXljb2xvdXJzLCBuYS52YWx1ZT0iZ3JleSIpICsKICB4bGFiKCJMaWJyYXJ5IikgKyB5bGFiKCJQcm9wb3J0aW9uIG9mIGNlbGxzIHBlciBsaWJyYXJ5IikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkNlbGwgdHlwZSIpCnByaW50KHApCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlMChmaWxlcHJlZml4LCAiZmlndXJlNWVfYWx0MS5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCgpwID0gZ2dwbG90KGNlbGxfdHlwZV9mcmFjX3dpZGVyKSArIAogIGFlcyh4PWxpYnJhcnksIHk9dmFsdWUsIGZpbGw9bmFtZSkgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbChyZXZlcnNlID0gVFJVRSkpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW15Y29sb3VycywgbmEudmFsdWU9ImdyZXkiKSArCiAgeGxhYigiTGlicmFyeSIpICsgeWxhYigiUHJvcG9ydGlvbiBvZiBjZWxscyBwZXIgbGlicmFyeSIpCnAgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KHAsICJDZWxsIHR5cGUiKQpwcmludChwKQpjb3dwbG90OjpzYXZlX3Bsb3QocGxvdD1wLAogICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSBwYXN0ZTAoZmlsZXByZWZpeCwgImZpZ3VyZTVlX2FsdDIucGRmIiksCiAgICAgICAgICAgICAgICAgICBiYXNlX2hlaWdodD00LAogICAgICAgICAgICAgICAgICAgYmFzZV93aWR0aD02KQpgYGAKCiMgQW5hbHlzaXMga2VyYXRpbm9jeXRlcwoKUmVydW4gaW50ZWdyYXRpb24gb24ga2VyYXRpbm9jeXRlcyBvbmx5LiBIZXJlIHdlIGRvIG5vdCBwZXJmb3JtIGNlbGwgY3ljbGUgYWRqdXN0bWVudCwgYXMgdGhlIGNlbGwgY3ljbGUgaXMgZGVlcGx5IGNvbm5lY3RlZCB0byB0aGUgdGlzc3VlIGR5bmFtaWNzIHdlJ3JlIGludGVyZXN0ZWQgaW4KYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQprZXJhdGlub2N5dGVfYmFyY29kZXMgPSByb3duYW1lcyhzZXVfaW50ZWdyYXRlZEBtZXRhLmRhdGEpW3NldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRjZWxsdHlwZT09ImtlcmF0aW5vY3l0ZSIgJiAhaXMubmEoc2V1X2ludGVncmF0ZWRAbWV0YS5kYXRhJGNlbGx0eXBlKV0KCiMgSW5pdGlhbCBydW4gZm91bmQgdGhlc2UgY2VsbHMgYXMgYW4gb3V0bGllciBjbHVzdGVyIGFuZCBleHByZXNzaW5nIGZpYnJvYmxhc3QgbWFya2Vycy4gSGVyZSB0aGVzZSBhcmUgYWxzbyByZW1vdmVkCm1pc2lkZW50aWZpZWRfZmlicm9ibGFzdHMgPSByZWFkLnRhYmxlKCIyMDIwMTIxMV9maWJyb2JsYXN0X2lkZW50aWZpZWRfYXNfa2VyYXRpbm9jeXRlLnR4dCIsIGhlYWRlcj1ULCBzdHJpbmdzQXNGYWN0b3JzPUYpCmtlcmF0aW5vY3l0ZV9iYXJjb2RlcyA9IGtlcmF0aW5vY3l0ZV9iYXJjb2Rlc1trZXJhdGlub2N5dGVfYmFyY29kZXMgJWluJSBtaXNpZGVudGlmaWVkX2ZpYnJvYmxhc3RzJGNlbGxbIW1pc2lkZW50aWZpZWRfZmlicm9ibGFzdHMkZmlicm9ibGFzdF9pZGVudGlmaWVkX2FzX2tlcmF0aW5vY3l0ZV1dCgpvdXRsaWVyX2NlbGxzID0gcmVhZC50YWJsZSgiMjAyMDEyMTFfbm90Y2gxX2NsdXN0ZXJfYXNzaWdubWVudHNfdG9fcmVtb3ZlX291dGxpZXJfY2VsbHMudHh0IiwgaGVhZGVyPVQsIHN0cmluZ3NBc0ZhY3RvcnM9RikKb3V0bGllcl9jZWxscyA9IG91dGxpZXJfY2VsbHNbb3V0bGllcl9jZWxscyRpc19vdXRsaWVyLF0Ka2VyYXRpbm9jeXRlX2JhcmNvZGVzID0ga2VyYXRpbm9jeXRlX2JhcmNvZGVzWyFrZXJhdGlub2N5dGVfYmFyY29kZXMgJWluJSBvdXRsaWVyX2NlbGxzJGNlbGxdCgpzZXVfa2VyYSA9IGxpc3QoKQpmb3IgKGkgaW4gMTpsZW5ndGgoZXhwcl9tYXRyaWNlcykpIHsKICBzZXVfa2VyYVtbaV1dID0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IGV4cHJfbWF0cmljZXNbW2ldXVssIGNvbG5hbWVzKGV4cHJfbWF0cmljZXNbW2ldXSkgJWluJSBrZXJhdGlub2N5dGVfYmFyY29kZXNdLCBtaW4uY2VsbHMgPSAzMCwgbWluLmZlYXR1cmVzID0gMzAwMCkKICAKICBtaXRvLmdlbmVzIDwtIHJvd25hbWVzKEdldEFzc2F5RGF0YShvYmplY3Q9c2V1X2tlcmFbW2ldXSkpW2dyZXBsKHBhdHRlcm4gPSAiXk1ULSIsIHggPSB0b3VwcGVyKHJvd25hbWVzKEdldEFzc2F5RGF0YShvYmplY3Q9c2V1X2tlcmFbW2ldXSkpKSldCiAgcGVyY2VudC5taXRvIDwtIE1hdHJpeDo6Y29sU3VtcyhHZXRBc3NheURhdGEob2JqZWN0PXNldV9rZXJhW1tpXV0sIHNsb3Q9ImNvdW50cyIpW21pdG8uZ2VuZXMsIF0pIC8gTWF0cml4Ojpjb2xTdW1zKEdldEFzc2F5RGF0YShvYmplY3Q9c2V1X2tlcmFbW2ldXSwgc2xvdD0iY291bnRzIikpCiAgc2V1X2tlcmFbW2ldXSA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBzZXVfa2VyYVtbaV1dLCBtZXRhZGF0YSA9IHBlcmNlbnQubWl0bywgY29sLm5hbWUgPSAicHJvcC5tdCIpCiAgCiAgIyBSZXN0cmljdCBwcm9wb3J0aW9uIE1UIGV4cHJlc3Npb24gdG8gcmVtb3ZlIHVuaGFwcHkgY2VsbHMKICBzZXVfa2VyYVtbaV1dID0gc3Vic2V0KHggPSBzZXVfa2VyYVtbaV1dLCBzdWJzZXQgPSBwcm9wLm10ID4gMC4wMyAmIHByb3AubXQgPCAwLjEpCiAgCiAgIyBSZW1vdmUgcG90ZW50aWFsIGRvdWJsZXRzCiAgc2V1W1tpXV0gPSBzdWJzZXQoeCA9IHNldVtbaV1dLCBzdWJzZXQgPSBuRmVhdHVyZV9STkEgPCA2NTAwICYgbkNvdW50X1JOQSA8IDU1MDAwKQogIAogIHNldV9rZXJhW1tpXV0gPSBTQ1RyYW5zZm9ybShzZXVfa2VyYVtbaV1dLCB2ZXJib3NlID0gRkFMU0UsIHZhcnMudG8ucmVncmVzcz1jKCJwcm9wLm10IiwgIm5GZWF0dXJlX1JOQSIsICJuQ291bnRfUk5BIikpCiAgc2V1X2tlcmFbW2ldXSA9IENlbGxDeWNsZVNjb3Jpbmcoc2V1X2tlcmFbW2ldXSwgcy5mZWF0dXJlcyA9IGNjLmdlbmVzJHMuZ2VuZXMsIGcybS5mZWF0dXJlcyA9IGNjLmdlbmVzJGcybS5nZW5lcywgYXNzYXkgPSAnU0NUJywgc2V0LmlkZW50ID0gVFJVRSkKICAjIEVuYWJsZSB0aGlzIHdoZW4gYWxzbyBhZGp1c3RpbmcgZm9yIGNlbGwgY3ljbGUKICAjIHNldVtbaV1dID0gU0NUcmFuc2Zvcm0oc2V1W1tpXV0sIHZlcmJvc2UgPSBGQUxTRSwgdmFycy50by5yZWdyZXNzPWMoInByb3AubXQiLCAibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiLCAiUy5TY29yZSIsICJHMk0uU2NvcmUiKSkKICAKICBzZXVbW2ldXSA9IEFkZE1ldGFEYXRhKG9iamVjdD1zZXVbW2ldXSwgbWV0YWRhdGE9cmVwKGxpYnJhcnlfbmFtZXNbaV0sIGxlbmd0aChzZXVbW2ldXSRuQ291bnRfUk5BKSksIGNvbC5uYW1lPSJsaWJyYXJ5IikKfQpgYGAKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KIyBUaGlzIGlzIHJlcXVpcmVkIGZvciBQcmVwU0NUSW50ZWdyYXRpb24sIGFuIGluY3JlYXNlIGZyb20gdGhlIFIgZGVmYXVsdCA1MTIgdG8gNTEyMCBmb3IgdGhpcyBkYXRhc2V0Cm9wdGlvbnMoZnV0dXJlLmdsb2JhbHMubWF4U2l6ZT01MTIwKjEwMjReMikKCnNldV9mZWF0dXJlcyA9IFNlbGVjdEludGVncmF0aW9uRmVhdHVyZXMob2JqZWN0Lmxpc3QgPSBzZXVfa2VyYSwgbmZlYXR1cmVzID0gMzAwMCkKc2V1X2tlcmEgPSBQcmVwU0NUSW50ZWdyYXRpb24ob2JqZWN0Lmxpc3QgPSBzZXVfa2VyYSwgYW5jaG9yLmZlYXR1cmVzID0gc2V1X2ZlYXR1cmVzLCB2ZXJib3NlID0gRkFMU0UpCnNldV9rZXJhID0gbGFwcGx5KFggPSBzZXVfa2VyYSwgRlVOID0gUnVuUENBLCB2ZXJib3NlID0gRkFMU0UsIGZlYXR1cmVzID0gc2V1X2ZlYXR1cmVzKQpzZXVfYW5jaG9ycyA9IEZpbmRJbnRlZ3JhdGlvbkFuY2hvcnMob2JqZWN0Lmxpc3QgPSBzZXVfa2VyYSwgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiU0NUIiwgYW5jaG9yLmZlYXR1cmVzID0gc2V1X2ZlYXR1cmVzLCB2ZXJib3NlID0gRkFMU0UsIHJlZHVjdGlvbiA9ICJycGNhIikKc2V1X2tlcmFfaW50ZWdyYXRlZCA9IEludGVncmF0ZURhdGEoYW5jaG9yc2V0ID0gc2V1X2FuY2hvcnMsIG5vcm1hbGl6YXRpb24ubWV0aG9kID0gIlNDVCIsIHZlcmJvc2UgPSBGQUxTRSkKbGlicmFyeV9wZXJfY2VsbCA9IGZhY3Rvcih1bmxpc3QobGFwcGx5KHNldSwgZnVuY3Rpb24oeCkgeEBtZXRhLmRhdGEkbGlicmFyeSkpLCBsZXZlbHM9bGlicmFyeV9uYW1lcykKbmFtZXMobGlicmFyeV9wZXJfY2VsbCkgPSB1bmxpc3QobGFwcGx5KHNldSwgZnVuY3Rpb24oeCkgcm93bmFtZXMoeEBtZXRhLmRhdGEpKSkKc2V1X2tlcmFfaW50ZWdyYXRlZCA9IEFkZE1ldGFEYXRhKG9iamVjdD1zZXVfa2VyYV9pbnRlZ3JhdGVkLCBtZXRhZGF0YT1saWJyYXJ5X3Blcl9jZWxsLCBjb2wubmFtZT0ibGlicmFyeSIpCmBgYAoKYGBge3J9CnNldV9rZXJhX2ludGVncmF0ZWQgPSBSdW5QQ0Eoc2V1X2tlcmFfaW50ZWdyYXRlZCwgdmVyYm9zZSA9IEZBTFNFKQoKVmxuUGxvdChzZXVfa2VyYV9pbnRlZ3JhdGVkLCBncm91cC5ieT0ib3JpZy5pZGVudCIsIGZlYXR1cmVzID0gYygibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiLCAicHJvcC5tdCIpLCBuY29sID0gMywgcHQuc2l6ZT0wLjAxKQpFbGJvd1Bsb3Qoc2V1X2tlcmFfaW50ZWdyYXRlZCwgbmRpbXM9NTApCmBgYApUaGlzIHRpbWUgd2UgcGljayAxMCBkaW1lbnNpb25zLCB0aGUgcG9pbnQgd2hlcmUgdGhlIHRhaWwgZmxhdHRlbnMgb2ZmCgpgYGB7cn0Kc2V1X2tlcmFfaW50ZWdyYXRlZCA9IFJ1blVNQVAoc2V1X2tlcmFfaW50ZWdyYXRlZCwgZGltcyA9IDE6MTApClVNQVBQbG90KHNldV9rZXJhX2ludGVncmF0ZWQpCnAgPSBVTUFQUGxvdChzZXVfa2VyYV9pbnRlZ3JhdGVkLGdyb3VwLmJ5PSJsaWJyYXJ5IikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkxpYnJhcnkiKSArIGdndGl0bGUoTlVMTCkgKyB4bGFiKCJVTUFQIDEiKSArIHlsYWIoIlVNQVAgMiIpCnByaW50KHApCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlMChmaWxlcHJlZml4LCAic3VwcGxmaWdfdW1hcF9rZXJhdGlub2N5dGVzX292ZXJsYXlfbGlicmFyeS5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCgoKVU1BUFBsb3Qoc2V1X2tlcmFfaW50ZWdyYXRlZCxncm91cC5ieT0iUGhhc2UiKQoKcGxvdF9kYXQgPSBhcy5kYXRhLmZyYW1lKHNldV9rZXJhX2ludGVncmF0ZWRAcmVkdWN0aW9uc1tbInVtYXAiXV1AY2VsbC5lbWJlZGRpbmdzKQpwbG90X2RhdCRrcnQxNCA9IGxvZyh0cG1bIktydDE0Iiwgcm93bmFtZXMoc2V1X2tlcmFfaW50ZWdyYXRlZEBtZXRhLmRhdGEpXSkKcGxvdF9kYXQkdGdtMyA9IGxvZyh0cG1bIlRnbTMiLCByb3duYW1lcyhzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSldKQpwbG90X2RhdCRrcnQ0ID0gbG9nKHRwbVsiS3J0NCIsIHJvd25hbWVzKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhKV0pCnBsb3RfZGF0JGxvciA9IGxvZyh0cG1bIkxvciIsIHJvd25hbWVzKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhKV0pCgpwMSA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QoZ2dwbG90KHBsb3RfZGF0KSArIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIsIGNvbG91cj1rcnQxNCkgKyBnZW9tX3BvaW50KHNpemU9MC4yKSArIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgKyB0aGVtZV9jb3dwbG90KCkgKyBnZ3RpdGxlKCJLcnQxNCIpLCBsZWdlbmRfdGl0bGUgPSAibG9nKFRQTSkiKQpwMiA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QoZ2dwbG90KHBsb3RfZGF0KSArIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIsIGNvbG91cj10Z20zKSArIGdlb21fcG9pbnQoc2l6ZT0wLjIpICsgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSArIHRoZW1lX2Nvd3Bsb3QoKSArIGdndGl0bGUoIlRnbTMiKSwgbGVnZW5kX3RpdGxlID0gImxvZyhUUE0pIikKcDMgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KGdncGxvdChwbG90X2RhdCkgKyBhZXMoeD1VTUFQXzEsIHk9VU1BUF8yLCBjb2xvdXI9a3J0NCkgKyBnZW9tX3BvaW50KHNpemU9MC4yKSArIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgKyB0aGVtZV9jb3dwbG90KCkgKyBnZ3RpdGxlKCJLcnQ0IiksIGxlZ2VuZF90aXRsZSA9ICJsb2coVFBNKSIpCnA0ID0gdGhlbWVfcHVibGljYXRpb25fcGxvdChnZ3Bsb3QocGxvdF9kYXQpICsgYWVzKHg9VU1BUF8xLCB5PVVNQVBfMiwgY29sb3VyPWxvcikgKyBnZW9tX3BvaW50KHNpemU9MC4yKSArIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgKyB0aGVtZV9jb3dwbG90KCkgKyBnZ3RpdGxlKCJMb3IiKSwgbGVnZW5kX3RpdGxlID0gImxvZyhUUE0pIikKcCA9IHBsb3RfZ3JpZChwMSwgcDIsIHAzLCBwNCwgbmNvbD0yKQpwcmludChwKQpjb3dwbG90OjpzYXZlX3Bsb3QocGxvdD1wLAogICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSBwYXN0ZTAoZmlsZXByZWZpeCwgInN1cHBsZmlnX2tlcmF0aW5vY3l0ZXNfbWFya2VyX2V4cHJlc3Npb24ucGRmIiksCiAgICAgICAgICAgICAgICAgICBiYXNlX2hlaWdodD00LAogICAgICAgICAgICAgICAgICAgYmFzZV93aWR0aD02KQoKVmxuUGxvdChzZXVfa2VyYV9pbnRlZ3JhdGVkLCBncm91cC5ieT0ib3JpZy5pZGVudCIsIGZlYXR1cmVzID0gInByb3AubXQiLCBuY29sID0gMSwgcHQuc2l6ZT0wLjAxKQoKRmVhdHVyZVBsb3Qoc2V1X2tlcmFfaW50ZWdyYXRlZCwgZmVhdHVyZXM9YygibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiLCAicHJvcC5tdCIpKQoKcGxvdF9kYXQgPSBhcy5kYXRhLmZyYW1lKHNldV9rZXJhX2ludGVncmF0ZWRAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncykKcGxvdF9kYXQkcGhhc2UgPSBzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRQaGFzZQpwbG90X2RhdCRwaGFzZSA9IGZhY3RvcihwbG90X2RhdCRwaGFzZSwgbGV2ZWxzPWMoIkcxIiwgIkcyTSIsICJTIikpCiMgbXljb2xvdXJzID0gYnJld2VyLnBhbCg0LCAiRGFyazIiKQpwID0gZ2dwbG90KHBsb3RfZGF0KSArIAogIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIsIGNvbG91cj1waGFzZSkgKyAKICBnZW9tX3BvaW50KHNpemU9MC4yKSArIAogIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkNlbGwgdHlwZSIpCnByaW50KHApCgpjb3dwbG90OjpzYXZlX3Bsb3QocGxvdD1wLAogICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSBwYXN0ZTAoZmlsZXByZWZpeCwgImZpZ3VyZTVoLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKCnBoYXNlX2NvdW50ID0gc2V1X2tlcmFfaW50ZWdyYXRlZEBtZXRhLmRhdGEgJT4lIGdyb3VwX2J5KGxpYnJhcnksIFBoYXNlKSAlPiUgc3VtbWFyaXNlKG49bigpKSAlPiUgbXV0YXRlKGZyYWMgPSBuL3N1bShuKSkKcGhhc2VfY291bnQkbGlicmFyeSA9IGZhY3RvcihwaGFzZV9jb3VudCRsaWJyYXJ5LCBsZXZlbHM9YygiV1QgMSIsICJXVCAyIiwgIkhPTSBLTyAxIiwgIkhPTSBLTyAyIikpCnAgPSBnZ3Bsb3QocGhhc2VfY291bnQpICsgCiAgYWVzKHg9bGlicmFyeSwgeT1mcmFjLCBmaWxsPVBoYXNlKSArIAogIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKyAKICB4bGFiKCJMaWJyYXJ5IikgKyB5bGFiKCJQcm9wb3J0aW9uIG9mIGNlbGxzIHBlciBsaWJyYXJ5IikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkN5Y2xlIHBoYXNlIikKcHJpbnQocCkKY293cGxvdDo6c2F2ZV9wbG90KHBsb3Q9cCwKICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gcGFzdGUwKGZpbGVwcmVmaXgsICJmaWd1cmU1aV9hbHQxLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKCnAgPSBnZ3Bsb3QocGhhc2VfY291bnQpICsgCiAgYWVzKHg9bGlicmFyeSwgeT1mcmFjLCBmaWxsPVBoYXNlKSArIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKHJldmVyc2UgPSBUUlVFKSkgKyAKICB4bGFiKCJMaWJyYXJ5IikgKyB5bGFiKCJQcm9wb3J0aW9uIG9mIGNlbGxzIHBlciBsaWJyYXJ5IikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkN5Y2xlIHBoYXNlIikKcHJpbnQocCkKY293cGxvdDo6c2F2ZV9wbG90KHBsb3Q9cCwKICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gcGFzdGUwKGZpbGVwcmVmaXgsICJmaWd1cmU1aV9hbHQyLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKCndyaXRlLnRhYmxlKHBoYXNlX2NvdW50WywgYygibGlicmFyeSIsICJQaGFzZSIsICJmcmFjIildICU+JSBwaXZvdF93aWRlcihuYW1lc19mcm9tPVBoYXNlLCB2YWx1ZXNfZnJvbT1mcmFjKSwgCiAgICAgICAgICAgIGZpbGU9cGFzdGUwKGZpbGVwcmVmaXgsICJmcmFjX2NlbGxzX2xpYnJhcnlfY2VsbGN5Y2xlLnR4dCIpLCBxdW90ZT1GLCBzZXA9Ilx0Iiwgcm93Lm5hbWVzPUYpCmBgYAoKUGxvdCB0aGUgY2x1c3RlciBhbm5vdGF0aW9ucyBpbiB0d28gd2F5cyAtIHRvIG1hdGNoIHRoZSBoZWF0bWFwIGZ1cnRoZXIgZG93bi4gCmBgYHtyfQoKc2V1X2tlcmFfaW50ZWdyYXRlZCA9IEZpbmROZWlnaGJvcnMoc2V1X2tlcmFfaW50ZWdyYXRlZCwgZGltcyA9IDE6MTApCnNldV9rZXJhX2ludGVncmF0ZWQgPSBGaW5kQ2x1c3RlcnMob2JqZWN0ID0gc2V1X2tlcmFfaW50ZWdyYXRlZCwgcmVzb2x1dGlvbiA9IDAuNiwgdmVyYm9zZT1GQUxTRSkKbWFya2Vyc19rZXJhdGlub2N5dGVzID0gRmluZEFsbE1hcmtlcnMoc2V1X2tlcmFfaW50ZWdyYXRlZCwgb25seS5wb3MgPSBUUlVFLCBtaW4ucGN0ID0gMC4xLCB0aHJlc2gudXNlID0gMC4yNSwgdmVyYm9zZSA9IEYpCnAgPC0gRGltUGxvdChvYmplY3QgPSBzZXVfa2VyYV9pbnRlZ3JhdGVkLCByZWR1Y3Rpb24gPSAidW1hcCIpCnAgPSBMYWJlbENsdXN0ZXJzKHBsb3QgPSBwLCBpZCA9ICdpZGVudCcsIHNpemU9NikKcCA9IHAgKyB4bGFiKCJVTUFQIDEiKSArIHlsYWIoIlVNQVAgMiIpCnAgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KHAsICJDbHVzdGVyIikgKyBnZ3RpdGxlKE5VTEwpCnByaW50KHApCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlMChmaWxlcHJlZml4LCAiY2x1c3Rlcl9hbm5vdGF0aW9ucy5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCgpzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnNfcmVvcmRlcmVkID0gZmFjdG9yKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycykpLCBsZXZlbHM9Yyg4LDYsOSw0LDEwLDAsMSwxMSwzLDUsMiw3KSkKcCA8LSBEaW1QbG90KG9iamVjdCA9IHNldV9rZXJhX2ludGVncmF0ZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAic2V1cmF0X2NsdXN0ZXJzX3Jlb3JkZXJlZCIpCnAgPSBMYWJlbENsdXN0ZXJzKHBsb3QgPSBwLCBpZCA9ICdzZXVyYXRfY2x1c3RlcnNfcmVvcmRlcmVkJywgc2l6ZT02KQpwID0gcCArIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkNsdXN0ZXIiKSArIGdndGl0bGUoTlVMTCkKcHJpbnQocCkKY293cGxvdDo6c2F2ZV9wbG90KHBsb3Q9cCwKICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gcGFzdGUwKGZpbGVwcmVmaXgsICJjbHVzdGVyX2Fubm90YXRpb25zX2FsdC5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCndyaXRlLnRhYmxlKG1hcmtlcnNfa2VyYXRpbm9jeXRlcywgZmlsZT1wYXN0ZTAoZmlsZXByZWZpeCwgInNpZ25pZl9leHByX21hcmtlcnNfa2VyYXRpbm9jeXRlX2FuYWx5c2lzLnR4dCIpLCBxdW90ZT1GLCBzZXA9Ilx0Iiwgcm93Lm5hbWVzPUYpCmBgYAoKIyMgRGV0ZXJtaW5lIG51bWJlciBvZiBiYXNhbCBjZWxscwoKYGBge3J9CiMgZXN0YWJsaXNoZWQgdG8gYnJvYWRseSBjYXB0dXJlIHRoZSBjaGFuZ2VvdmVyIHBvaW50IHdoZXJlIEtydDE0IGRyb3BzIG9mIGFuZCBUZ20zIHN0YXJ0cyBnb2luZyB1cApzbG9wZSA9IC0xLjUKaW50ZXJjZXB0ID0gLTQuNQoKYWRkX3RocmVzaG9sZCA9IGZ1bmN0aW9uKHAsIHNsb3BlLCBpbnRlcmNlcHQpIHsKICByZXR1cm4ocCArIGdlb21fYWJsaW5lKHNsb3BlPXNsb3BlLCBpbnRlcmNlcHQ9aW50ZXJjZXB0LCBjb2xvdXI9ImJsYWNrIiwgbGluZXR5cGU9MikpCn0KCnBjMSA9IGFzLmRhdGEuZnJhbWUoc2V1X2tlcmFfaW50ZWdyYXRlZEByZWR1Y3Rpb25zW1sidW1hcCJdXUBjZWxsLmVtYmVkZGluZ3NbLCBjKDEsIDIpLCBkcm9wPUZdKQpwYzEkbGlicmFyeSA9IGFzLmNoYXJhY3RlcihzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRsaWJyYXJ5W21hdGNoKHJvd25hbWVzKHBjMSksIHJvd25hbWVzKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhKSldKQpwYzEkc2VsZWN0ZWQgPSBpZmVsc2UocGMxJFVNQVBfMiA8IChpbnRlcmNlcHQrc2xvcGUqcGMxJFVNQVBfMSksIDAsIDEpCnBjMSRzZWxlY3RlZCA9IGZhY3RvcihwYzEkc2VsZWN0ZWQsIGxldmVscz1jKDEsIDApKQpwYzEkbGF5ZXIgPSAiQmFzYWwiCnBjMSRsYXllcltwYzEkc2VsZWN0ZWQ9PSIwIl0gPSAiU3VwcmFiYXNhbCIKcGMxJGxheWVyID0gZmFjdG9yKHBjMSRsYXllciwgbGV2ZWxzPWMoIkJhc2FsIiwgIlN1cHJhYmFzYWwiKSkKYGBgCgpQbG90IHRoZSBleHByZXNzaW9uIG9mIGEgbnVtYmVyIG9mIG1hcmtlcnMgcmVsYXRpdmUgdG8gdGhlIHNlbGVjdGVkIHRocmVzaG9sZCB0byBzaG93IHdlJ3ZlIGJyb2FkbHkgY2FwdHVyZWQgdGhlIGNyb3Nzb3ZlciBhcmVhCmBgYHtyfQpwbG90X2RhdCA9IGFzLmRhdGEuZnJhbWUoc2V1X2tlcmFfaW50ZWdyYXRlZEByZWR1Y3Rpb25zW1sidW1hcCJdXUBjZWxsLmVtYmVkZGluZ3MpCnBsb3RfZGF0JGtydDE0ID0gbG9nKHRwbVsiS3J0MTQiLCByb3duYW1lcyhzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSldKQpwbG90X2RhdCR0Z20zID0gbG9nKHRwbVsiVGdtMyIsIHJvd25hbWVzKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhKV0pCnBsb3RfZGF0JGtydDQgPSBsb2codHBtWyJLcnQ0Iiwgcm93bmFtZXMoc2V1X2tlcmFfaW50ZWdyYXRlZEBtZXRhLmRhdGEpXSkKcGxvdF9kYXQkbG9yID0gbG9nKHRwbVsiTG9yIiwgcm93bmFtZXMoc2V1X2tlcmFfaW50ZWdyYXRlZEBtZXRhLmRhdGEpXSkKCnAxID0gdGhlbWVfcHVibGljYXRpb25fcGxvdChnZ3Bsb3QocGxvdF9kYXQpICsgYWVzKHg9VU1BUF8xLCB5PVVNQVBfMiwgY29sb3VyPWtydDE0KSArIGdlb21fcG9pbnQoc2l6ZT0wLjIpICsgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSArIHRoZW1lX2Nvd3Bsb3QoKSArIGdndGl0bGUoIktydDE0IiksIGxlZ2VuZF90aXRsZSA9ICJsb2coVFBNKSIpCnAyID0gdGhlbWVfcHVibGljYXRpb25fcGxvdChnZ3Bsb3QocGxvdF9kYXQpICsgYWVzKHg9VU1BUF8xLCB5PVVNQVBfMiwgY29sb3VyPXRnbTMpICsgZ2VvbV9wb2ludChzaXplPTAuMikgKyB4bGFiKCJVTUFQIDEiKSArIHlsYWIoIlVNQVAgMiIpICsgdGhlbWVfY293cGxvdCgpICsgZ2d0aXRsZSgiVGdtMyIpLCBsZWdlbmRfdGl0bGUgPSAibG9nKFRQTSkiKQpwMyA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QoZ2dwbG90KHBsb3RfZGF0KSArIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIsIGNvbG91cj1rcnQ0KSArIGdlb21fcG9pbnQoc2l6ZT0wLjIpICsgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSArIHRoZW1lX2Nvd3Bsb3QoKSArIGdndGl0bGUoIktydDQiKSwgbGVnZW5kX3RpdGxlID0gImxvZyhUUE0pIikKcDQgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KGdncGxvdChwbG90X2RhdCkgKyBhZXMoeD1VTUFQXzEsIHk9VU1BUF8yLCBjb2xvdXI9bG9yKSArIGdlb21fcG9pbnQoc2l6ZT0wLjIpICsgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSArIHRoZW1lX2Nvd3Bsb3QoKSArIGdndGl0bGUoIkxvciIpLCBsZWdlbmRfdGl0bGUgPSAibG9nKFRQTSkiKQpwID0gcGxvdF9ncmlkKGFkZF90aHJlc2hvbGQocDEsIHNsb3BlLCBpbnRlcmNlcHQpLCAKICAgICAgICAgICAgICBhZGRfdGhyZXNob2xkKHAyLCBzbG9wZSwgaW50ZXJjZXB0KSwgCiAgICAgICAgICAgICAgYWRkX3RocmVzaG9sZChwMywgc2xvcGUsIGludGVyY2VwdCksIAogICAgICAgICAgICAgIGFkZF90aHJlc2hvbGQocDQsIHNsb3BlLCBpbnRlcmNlcHQpLCBuY29sPTIpCnByaW50KHApCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlMChmaWxlcHJlZml4LCAic3VwcGxmaWdfYmFzYWxfY2VsbF90aHJlc2hvbGRfdnNfbWFya2Vycy5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCmBgYAoKUGxvdCB0aGUgdGhyZXNob2xkIHBlciBsaWJyYXJ5IHRvIHNob3cgd2hpY2ggY2VsbHMgaGF2ZSBiZWVuIG1hcmtlZCBhcyBiYXNhbCBjZWxscwpgYGB7cn0KbXljb2xvdXJzID0gYygicmVkIiwgImdyZXkiKQpwMSA9IGdncGxvdChwYzFbcGMxJGxpYnJhcnk9PSJXVCAxIixdKSArCiAgYWVzKHg9VU1BUF8xLCB5PVVNQVBfMiwgY29sb3VyPWxheWVyKSArCiAgZ2VvbV9wb2ludChzaXplPTAuMikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPW15Y29sb3VycywgbmEudmFsdWU9ImdyZXkiKSArCiAgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSArIGdndGl0bGUoIldUIDEiKSArIHRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCgpwMiA9IGdncGxvdChwYzFbcGMxJGxpYnJhcnk9PSJXVCAyIixdKSArCiAgYWVzKHg9VU1BUF8xLCB5PVVNQVBfMiwgY29sb3VyPWxheWVyKSArCiAgZ2VvbV9wb2ludChzaXplPTAuMikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPW15Y29sb3VycywgbmEudmFsdWU9ImdyZXkiKSArCiAgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSArIGdndGl0bGUoIldUIDIiKSArIHRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpIAoKcDMgPSBnZ3Bsb3QocGMxW3BjMSRsaWJyYXJ5PT0iSE9NIEtPIDEiLF0pICsKICBhZXMoeD1VTUFQXzEsIHk9VU1BUF8yLCBjb2xvdXI9bGF5ZXIpICsKICBnZW9tX3BvaW50KHNpemU9MC4yKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9bXljb2xvdXJzLCBuYS52YWx1ZT0iZ3JleSIpICsKICB4bGFiKCJVTUFQIDEiKSArIHlsYWIoIlVNQVAgMiIpICsgZ2d0aXRsZSgiSE9NIEtPIDEiKSArIHRoZW1lX2Nvd3Bsb3QoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCgpwNCA9IGdncGxvdChwYzFbcGMxJGxpYnJhcnk9PSJIT00gS08gMiIsXSkgKwogIGFlcyh4PVVNQVBfMSwgeT1VTUFQXzIsIGNvbG91cj1sYXllcikgKwogIGdlb21fcG9pbnQoc2l6ZT0wLjIpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1teWNvbG91cnMsIG5hLnZhbHVlPSJncmV5IikgKwogIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgKyBnZ3RpdGxlKCJIT00gS08gMiIpICsgdGhlbWVfY293cGxvdCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikKCnAgPSBwbG90X2dyaWQoYWRkX3RocmVzaG9sZChwMSwgc2xvcGUsIGludGVyY2VwdCksIAogICAgICAgICAgICAgIGFkZF90aHJlc2hvbGQocDIsIHNsb3BlLCBpbnRlcmNlcHQpLCAKICAgICAgICAgICAgICBhZGRfdGhyZXNob2xkKHAzLCBzbG9wZSwgaW50ZXJjZXB0KSwgCiAgICAgICAgICAgICAgYWRkX3RocmVzaG9sZChwNCwgc2xvcGUsIGludGVyY2VwdCkpCnByaW50KHApCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlMChmaWxlcHJlZml4LCAic3VwcGxmaWdfYmFzYWxfY2VsbF9jbGFzc2lmaWNhdGlvbl9tYXAucGRmIiksCiAgICAgICAgICAgICAgICAgICBiYXNlX2hlaWdodD00LAogICAgICAgICAgICAgICAgICAgYmFzZV93aWR0aD02KQpgYGAKClN1bW1hcmlzZSB0aGUgY2FsbHMgaW50byBhIHRhYmxlCmBgYHtyfQpiYXNhbF9jZWxsX2NvdW50ID0gcGMxICU+JSBncm91cF9ieShzZWxlY3RlZCwgbGlicmFyeSkgJT4lIHN1bW1hcmlzZShuPW4oKSkgJT4lIHBpdm90X3dpZGVyKG5hbWVzX2Zyb209c2VsZWN0ZWQsIHZhbHVlc19mcm9tPW4pCmNvbG5hbWVzKGJhc2FsX2NlbGxfY291bnQpWzI6M10gPSBjKCJudW1fYmFzYWwiLCAibnVtX3N1cHJhYmFzYWwiKQpiYXNhbF9jZWxsX2NvdW50WywgYygiZnJhY19iYXNhbCIsICJmcmFjX3N1cHJhYmFzYWwiKV0gPSBiYXNhbF9jZWxsX2NvdW50WywgMjozXSAvIHJvd1N1bXMoYmFzYWxfY2VsbF9jb3VudFssIDI6M10pCnByaW50KGJhc2FsX2NlbGxfY291bnQpCndyaXRlLnRhYmxlKGJhc2FsX2NlbGxfY291bnQsIGZpbGU9cGFzdGUwKGZpbGVwcmVmaXgsICJub3RjaDFfZnJhY3Rpb25fYmFzYWxfY2VsbHNfdW1hcF90aHJlc2hvbGQudHh0IiksIHF1b3RlPUYsIHNlcD0iXHQiLCByb3cubmFtZXM9RikKYGBgCgpNYWtlIHRoZSBzdW1tYXJ5IGZpZ3VyZXMgZm9yIHRoZSBwYXBlcgpgYGB7cn0KCm15Y29sb3VycyA9IGMoInJlZCIsICJncmV5IikKcCA9IGdncGxvdChwYzEpICsgCiAgYWVzKHg9VU1BUF8xLCB5PVVNQVBfMiwgY29sb3VyPWxheWVyKSArIAogIGdlb21fcG9pbnQoc2l6ZT0wLjIpICsgCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9bXljb2xvdXJzLCBuYS52YWx1ZT0iZ3JleSIpICsKICB4bGFiKCJVTUFQIDEiKSArIHlsYWIoIlVNQVAgMiIpCnAgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KHAsICJMYXllciIpCnByaW50KHApCgpjb3dwbG90OjpzYXZlX3Bsb3QocGxvdD1wLAogICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSBwYXN0ZTAoZmlsZXByZWZpeCwgImZpZ3VyZTVmLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKCmJhc2FsX2NlbGxfY291bnQgPSBiYXNhbF9jZWxsX2NvdW50WyxjKCJsaWJyYXJ5IiwgImZyYWNfYmFzYWwiLCAiZnJhY19zdXByYWJhc2FsIildCmJhc2FsX2NlbGxfY291bnQkbGlicmFyeSA9IGZhY3RvcihiYXNhbF9jZWxsX2NvdW50JGxpYnJhcnksIGxldmVscz1jKCJXVCAxIiwgIldUIDIiLCAiSE9NIEtPIDEiLCAiSE9NIEtPIDIiKSkKY29sbmFtZXMoYmFzYWxfY2VsbF9jb3VudClbMjozXSA9IGMoIkJhc2FsIiwgIlN1cHJhYmFzYWwiKQpwID0gZ2dwbG90KGJhc2FsX2NlbGxfY291bnQgJT4lIHBpdm90X2xvbmdlcighbGlicmFyeSkpICsgCiAgYWVzKHg9bGlicmFyeSwgeT12YWx1ZSwgZmlsbD1uYW1lKSArIAogIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9bXljb2xvdXJzLCBuYS52YWx1ZT0iZ3JleSIpICsKICB4bGFiKCJMaWJyYXJ5IikgKyB5bGFiKCJQcm9wb3J0aW9uIG9mIGNlbGxzIHBlciBsaWJyYXJ5IikKcCA9IHRoZW1lX3B1YmxpY2F0aW9uX3Bsb3QocCwgIkxheWVyIikKcHJpbnQocCkKY293cGxvdDo6c2F2ZV9wbG90KHBsb3Q9cCwKICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gcGFzdGUwKGZpbGVwcmVmaXgsICJmaWd1cmU1Z19hbHQxLnBkZiIpLAogICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgIGJhc2Vfd2lkdGg9NikKCnAgPSBnZ3Bsb3QoYmFzYWxfY2VsbF9jb3VudCAlPiUgcGl2b3RfbG9uZ2VyKCFsaWJyYXJ5KSkgKyAKICBhZXMoeD1saWJyYXJ5LCB5PXZhbHVlLCBmaWxsPW5hbWUpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbChyZXZlcnNlID0gVFJVRSkpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW15Y29sb3VycywgbmEudmFsdWU9ImdyZXkiKSArCiAgeGxhYigiTGlicmFyeSIpICsgeWxhYigiUHJvcG9ydGlvbiBvZiBjZWxscyBwZXIgbGlicmFyeSIpCnAgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KHAsICJMYXllciIpCnByaW50KHApCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlMChmaWxlcHJlZml4LCAiZmlndXJlNWdfYWx0Mi5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCmBgYApTYXZlIHRoZSBvdXRjb21lCmBgYHtyfQojIHNhdmUgdGhlIEtydDQgZXN0aW1hdGVzCm1ldGFkYXQgPSBzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YQptZXRhZGF0JGNlbGwgPSByb3duYW1lcyhtZXRhZGF0KQoKbWV0YWRhdCA9IG1ldGFkYXRbLCBjKCJjZWxsIiwgIm9yaWcuaWRlbnQiLCAiUGhhc2UiLCAicHJvcC5tdCIsICJuQ291bnRfUk5BIiwgIm5GZWF0dXJlX1JOQSIpXQpjb2xuYW1lcyhtZXRhZGF0KVsyXSA9ICJsaWJyYXJ5IgoKbWV0YWRhdCRpc19iYXNhbCA9IHBjMSRsYXllcj09IkJhc2FsIgp3cml0ZS50YWJsZShtZXRhZGF0LCBmaWxlPXBhc3RlMChmaWxlcHJlZml4LCAibm90Y2gxX2Jhc2FsX3N1cHJhYmFzYWxfY2FsbHNfdW1hcF90aHJlc2hvbGQudHh0IiksIHF1b3RlPUYsIHNlcD0iXHQiLCByb3cubmFtZXM9RikKYGBgCgoKUGxvdCBwZXJjZW50YWdlIGJhc2FsIGNlbGxzIHBlciBjZWxsIGN5Y2xlIHBoYXNlCgpgYGB7cn0KcGhhc2VfY291bnQgPSBzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YVttZXRhZGF0JGlzX2Jhc2FsLF0gJT4lIGdyb3VwX2J5KGxpYnJhcnksIFBoYXNlKSAlPiUgc3VtbWFyaXNlKG49bigpKSAlPiUgbXV0YXRlKGZyYWMgPSBuL3N1bShuKSkKcGhhc2VfY291bnQkbGlicmFyeSA9IGZhY3RvcihwaGFzZV9jb3VudCRsaWJyYXJ5LCBsZXZlbHM9YygiV1QgMSIsICJXVCAyIiwgIkhPTSBLTyAxIiwgIkhPTSBLTyAyIikpCgpwID0gZ2dwbG90KHBoYXNlX2NvdW50KSArIAogIGFlcyh4PWxpYnJhcnksIHk9ZnJhYywgZmlsbD1QaGFzZSkgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbChyZXZlcnNlID0gVFJVRSkpICsgCiAgeGxhYigiTGlicmFyeSIpICsgeWxhYigiUHJvcG9ydGlvbiBvZiBjZWxscyBwZXIgbGlicmFyeSIpCnAgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KHAsICJDeWNsZSBwaGFzZSIpCnByaW50KHApCgpwID0gZ2dwbG90KHBoYXNlX2NvdW50KSArIAogIGFlcyh4PWxpYnJhcnksIHk9biwgZmlsbD1QaGFzZSkgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKyAKICB4bGFiKCJMaWJyYXJ5IikgKyB5bGFiKCJOdW1iZXIgb2YgY2VsbHMgcGVyIGxpYnJhcnkiKQpwID0gdGhlbWVfcHVibGljYXRpb25fcGxvdChwLCAiQ3ljbGUgcGhhc2UiKQpwcmludChwKQpgYGAKCgoKYGBge3J9CnNhdmUoZmlsZT1wYXN0ZTAoZmlsZXByZWZpeCwgIm5vdGNoMV9rZXJhdGlub2N5dGVzX2JhdGNoX2VmZmVjdF93aXRoX2ZpbHRlcl93aXRoX2FkanVzdG1lbnQuUkRhdGEiKSwgc2V1X2tlcmFfaW50ZWdyYXRlZCwgbWFya2Vyc19rZXJhdGlub2N5dGVzKQpgYGAKCgoKIyMgQWRkaXRpb25hbCBwbG90cwoKRG93bnNhbXBsZWQgcGxvdCBzaG93aW5nIGVxdWFsIGNlbGxzIHBlciBsaWJyYXJ5CmBgYHtyfQpjb3VudHNfcGVyX2xpYnJhcnkgPSB0YWJsZShzZXVfaW50ZWdyYXRlZEBtZXRhLmRhdGEkbGlicmFyeSkKc2V0LnNlZWQoMTIzKQpzZWxlY3RlZF9jZWxscyA9IHJvd25hbWVzKHNldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSlbc2V1X2ludGVncmF0ZWRAbWV0YS5kYXRhJGxpYnJhcnk9PSJXVCAxIl1bc2FtcGxlKDE6Y291bnRzX3Blcl9saWJyYXJ5WyJXVCAxIl0sIDE1MDApXQoKc2VsZWN0ZWRfY2VsbHMgPSBjKHNlbGVjdGVkX2NlbGxzLCByb3duYW1lcyhzZXVfaW50ZWdyYXRlZEBtZXRhLmRhdGEpW3NldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRsaWJyYXJ5PT0iV1QgMiJdW3NhbXBsZSgxOmNvdW50c19wZXJfbGlicmFyeVsiV1QgMiJdLCAxNTAwKV0pCgpzZWxlY3RlZF9jZWxscyA9IGMoc2VsZWN0ZWRfY2VsbHMsIHJvd25hbWVzKHNldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSlbc2V1X2ludGVncmF0ZWRAbWV0YS5kYXRhJGxpYnJhcnk9PSJIT00gS08gMSJdW3NhbXBsZSgxOmNvdW50c19wZXJfbGlicmFyeVsiSE9NIEtPIDEiXSwgMTUwMCldKQoKc2VsZWN0ZWRfY2VsbHMgPSBjKHNlbGVjdGVkX2NlbGxzLCByb3duYW1lcyhzZXVfaW50ZWdyYXRlZEBtZXRhLmRhdGEpW3NldV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRsaWJyYXJ5PT0iSE9NIEtPIDIiXVtzYW1wbGUoMTpjb3VudHNfcGVyX2xpYnJhcnlbIkhPTSBLTyAyIl0sIDE1MDApXSkKCgoKc2V1X2ludGVncmF0ZWRfdGVtcCA9IHN1YnNldChzZXVfaW50ZWdyYXRlZCwgY2VsbHM9c2VsZWN0ZWRfY2VsbHMpCgpwID0gVU1BUFBsb3Qoc2V1X2ludGVncmF0ZWRfdGVtcCxncm91cC5ieT0ibGlicmFyeSIpCnAgPSB0aGVtZV9wdWJsaWNhdGlvbl9wbG90KHAsICJMaWJyYXJ5IikgKyBnZ3RpdGxlKE5VTEwpICsgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKQpwcmludChwKQpjb3dwbG90OjpzYXZlX3Bsb3QocGxvdD1wLAogICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSBwYXN0ZTAoZmlsZXByZWZpeCwgInN1cHBsZmlnX3VtYXBfb3ZlcmxheV9saWJyYXJ5X2Rvd25zYW1wbGVkMTUwMC5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCgpjb3VudHNfcGVyX2xpYnJhcnkgPSB0YWJsZShzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRsaWJyYXJ5KQpzZXQuc2VlZCgxMjMpCnNlbGVjdGVkX2NlbGxzID0gcm93bmFtZXMoc2V1X2tlcmFfaW50ZWdyYXRlZEBtZXRhLmRhdGEpW3NldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhJGxpYnJhcnk9PSJXVCAxIl1bc2FtcGxlKDE6Y291bnRzX3Blcl9saWJyYXJ5WyJXVCAxIl0sIDE0MDApXQoKc2VsZWN0ZWRfY2VsbHMgPSBjKHNlbGVjdGVkX2NlbGxzLCByb3duYW1lcyhzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSlbc2V1X2tlcmFfaW50ZWdyYXRlZEBtZXRhLmRhdGEkbGlicmFyeT09IldUIDIiXVtzYW1wbGUoMTpjb3VudHNfcGVyX2xpYnJhcnlbIldUIDIiXSwgMTQwMCldKQoKc2VsZWN0ZWRfY2VsbHMgPSBjKHNlbGVjdGVkX2NlbGxzLCByb3duYW1lcyhzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSlbc2V1X2tlcmFfaW50ZWdyYXRlZEBtZXRhLmRhdGEkbGlicmFyeT09IkhPTSBLTyAxIl1bc2FtcGxlKDE6Y291bnRzX3Blcl9saWJyYXJ5WyJIT00gS08gMSJdLCAxNDAwKV0pCgpzZWxlY3RlZF9jZWxscyA9IGMoc2VsZWN0ZWRfY2VsbHMsIHJvd25hbWVzKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhKVtzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRsaWJyYXJ5PT0iSE9NIEtPIDIiXVtzYW1wbGUoMTpjb3VudHNfcGVyX2xpYnJhcnlbIkhPTSBLTyAyIl0sIDE0MDApXSkKCgpzZXVfaW50ZWdyYXRlZF90ZW1wID0gc3Vic2V0KHNldV9rZXJhX2ludGVncmF0ZWQsIGNlbGxzPXNlbGVjdGVkX2NlbGxzKQoKcCA9IFVNQVBQbG90KHNldV9pbnRlZ3JhdGVkX3RlbXAsZ3JvdXAuYnk9ImxpYnJhcnkiKQpwID0gdGhlbWVfcHVibGljYXRpb25fcGxvdChwLCAiTGlicmFyeSIpICsgZ2d0aXRsZShOVUxMKSArIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikKcHJpbnQocCkKY293cGxvdDo6c2F2ZV9wbG90KHBsb3Q9cCwKICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gcGFzdGUwKGZpbGVwcmVmaXgsICJzdXBwbGZpZ191bWFwX292ZXJsYXlfbGlicmFyeV9rZXJhdGlub2N5dGVzX2Rvd25zYW1wbGVkMTQwMC5wZGYiKSwKICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTYpCmBgYAoKYGBge3J9CiMgbG9hZCgiMjAyMTA3MTdfbm90Y2gxX2JhdGNoX2VmZmVjdF93aXRoX2ZpbHRlcl93aXRoX2FkanVzdG1lbnQuUkRhdGEiKQpgYGAKClBsb3QgaGVhdG1hcCB3aXRoIHNlbGVjdGVkIG1hcmtlciBnZW5lcyBmcm9tIE1jR2luIGV0IGFsIGV4cHJlc3Npb24gcGVyIGNsdXN0ZXIKCmBgYHtyfQpsb2FkKHBhc3RlMChmaWxlcHJlZml4LCAibm90Y2gxX2tlcmF0aW5vY3l0ZXNfYmF0Y2hfZWZmZWN0X3dpdGhfZmlsdGVyX3dpdGhfYWRqdXN0bWVudC5SRGF0YSIpKQpiYXNhbF9tYXJrZXJzID0gYygiQ2RoMyIsICJJdGdiMSIsICJLcnQxNSIsICJLcnQxNCIsICJLcnQ1IiwgIkNvbDE3YTEiLCAiU294MiIsICJUcnA2MyIpCmN5Y2xlX21hcmtlcnMgPSBjKCJHbW5uIiwgIk1jbTYiLCAiTWNtMiIsICJDZHQxIiwgIlBjbmEiLCAiQ2NuZTEiLCAiRTJmMSIsICJDY25lMiIsICJDZGM2IiwgIkF1cmtiIiwgIlRvcDJhIiwgIkNjbmIyIiwgIkJ1YjEiLCAiVWJlMmMiLCAiQXVya2EiLCAiS2lmMjMiLCAiQ2NuYjEiLCAiTWtpNjciLCAiTWFkMmwxIiwgIkJpcmM1IikKZGlmZl9tYXJrZXJzID0gYygiS3J0MTMiLCAiS2xmNCIsICJUZ20zIiwgIlNic24iLCAiR3JobDMiLCAiS3J0NCIsICJOb3RjaDMiKQpzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnNfcmVvcmRlcmVkID0gZmFjdG9yKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycykpLCBsZXZlbHM9Yyg4LDYsOSw0LDEwLDAsMSwxMSwzLDUsMiw3KSkKIyBwID0gRG9IZWF0bWFwKHNldV9rZXJhX2ludGVncmF0ZWQsIGZlYXR1cmVzID0gYyhiYXNhbF9tYXJrZXJzLCBjeWNsZV9tYXJrZXJzLCBkaWZmX21hcmtlcnMpLCBncm91cC5ieT0ic2V1cmF0X2NsdXN0ZXJzX3Jlb3JkZXJlZCIpICsgTm9MZWdlbmQoKQojIHByaW50KHApCiMgY293cGxvdDo6c2F2ZV9wbG90KHBsb3Q9cCwKIyAgICAgICAgICAgICAgICAgICAgIGZpbGVuYW1lID0gImZpZ3VyZV9oZWF0bWFwX2FsbF9saWJyYXJpZXMucGRmIiwKIyAgICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiMgICAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTgpCmBgYAoKYGBge3J9CnAgPSBEb0hlYXRtYXAoc2V1X2tlcmFfaW50ZWdyYXRlZCwgCiAgICAgICAgICBjZWxscyA9IHJvd25hbWVzKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhKVtzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRvcmlnLmlkZW50ICVpbiUgYygiYzEiLCAiYzIiKV0sCiAgICAgICAgICBmZWF0dXJlcyA9IGMoYmFzYWxfbWFya2VycywgY3ljbGVfbWFya2VycywgZGlmZl9tYXJrZXJzKSwgZ3JvdXAuYnk9InNldXJhdF9jbHVzdGVyc19yZW9yZGVyZWQiKSArIE5vTGVnZW5kKCkKcHJpbnQocCkKY293cGxvdDo6c2F2ZV9wbG90KHBsb3Q9cCwKICAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9ICJmaWd1cmVfaGVhdG1hcF9jb250cm9sX2xpYnJhcmllcy5wZGYiLAogICAgICAgICAgICAgICAgICAgIGJhc2VfaGVpZ2h0PTQsCiAgICAgICAgICAgICAgICAgICAgYmFzZV93aWR0aD04KQpwID0gRG9IZWF0bWFwKHNldV9rZXJhX2ludGVncmF0ZWQsIAogICAgICAgICAgY2VsbHMgPSByb3duYW1lcyhzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSlbc2V1X2tlcmFfaW50ZWdyYXRlZEBtZXRhLmRhdGEkb3JpZy5pZGVudCAlaW4lIGMoInMxIiwgInMyIildLAogICAgICAgICAgZmVhdHVyZXMgPSBjKGJhc2FsX21hcmtlcnMsIGN5Y2xlX21hcmtlcnMsIGRpZmZfbWFya2VycyksIGdyb3VwLmJ5PSJzZXVyYXRfY2x1c3RlcnNfcmVvcmRlcmVkIikgKyBOb0xlZ2VuZCgpCnByaW50KHApCmNvd3Bsb3Q6OnNhdmVfcGxvdChwbG90PXAsCiAgICAgICAgICAgICAgICAgICAgZmlsZW5hbWUgPSAiZmlndXJlX2hlYXRtYXBfa29fbGlicmFyaWVzLnBkZiIsCiAgICAgICAgICAgICAgICAgICAgYmFzZV9oZWlnaHQ9NCwKICAgICAgICAgICAgICAgICAgICBiYXNlX3dpZHRoPTgpCgpgYGAKU2F2ZSBhIHRhYmxlIHdpdGggY291bnRzIHBlciBjbHVzdGVyIHBlciBsaWJyYXJ5CmBgYHtyfQpjbHVzdGVyX2NvdW50cyA9IGxpc3QoKQpmb3IgKGNsdXN0ZXIgaW4gdW5pcXVlKHNldV9rZXJhX2ludGVncmF0ZWRAbWV0YS5kYXRhJHNldXJhdF9jbHVzdGVycykpIHsKICByZXMgPSBzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YVtzZXVfa2VyYV9pbnRlZ3JhdGVkQG1ldGEuZGF0YSRzZXVyYXRfY2x1c3RlcnM9PWNsdXN0ZXIsXSAlPiUgZ3JvdXBfYnkobGlicmFyeSkgJT4lIHN1bW1hcmlzZShuPW4oKSkKICByZXMkY2x1c3RlciA9IGNsdXN0ZXIKICBjbHVzdGVyX2NvdW50c1tbY2x1c3Rlcl1dID0gcmVzCn0KY2x1c3Rlcl9jb3VudHMgPSBkby5jYWxsKHJiaW5kLCBjbHVzdGVyX2NvdW50cykKd3JpdGUudGFibGUoY2x1c3Rlcl9jb3VudHMsIGZpbGU9cGFzdGUwKGZpbGVwcmVmaXgsICJfY2x1c3Rlcl9saWJyYXJ5X2NlbGxfY291bnRzLnR4dCIpLCBxdW90ZT1GLCBzZXA9Ilx0Iiwgcm93Lm5hbWVzPUYpCmBgYAoK